[MM-11879] Upgrade to RNN v2

This commit is contained in:
Harrison Healey 2019-07-15 15:39:39 -04:00
commit 175c0df5d9
252 changed files with 3767 additions and 4141 deletions

View file

@ -90,7 +90,6 @@ post-install:
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-messageformat/package.json
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-relativeformat/package.json
@sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json
@sed -i'' -e "s|super.onBackPressed();|this.moveTaskToBack(true);|g" node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java
@sed -i'' -e "s|compile 'com.facebook.react:react-native:0.17.+'|compile 'com.facebook.react:react-native:+'|g" node_modules/react-native-bottom-sheet/android/build.gradle
@if [ $(shell grep "const Platform" node_modules/react-native/Libraries/Lists/VirtualizedList.js | grep -civ grep) -eq 0 ]; then \
sed $ -i'' -e "s|const ReactNative = require('ReactNative');|const ReactNative = require('ReactNative');`echo $\\\\\\r;`const Platform = require('Platform');|g" node_modules/react-native/Libraries/Lists/VirtualizedList.js; \
@ -161,7 +160,7 @@ run-android: | check-device-android pre-run prepare-android-build ## Runs the ap
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo Running Android app in development; \
if [ ! -z ${VARIANT} ]; then \
if [ ! -z ${VARIANT} ]; then \
react-native run-android --no-packager --variant=${VARIANT}; \
else \
react-native run-android --no-packager; \

View file

@ -122,6 +122,7 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5"
versionCode 207
versionName "1.21.0"
multiDexEnabled = true

View file

@ -2,12 +2,14 @@ package com.mattermost.rnbeta;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.reactnativenavigation.controllers.SplashActivity;
public class MainActivity extends SplashActivity {
import com.reactnativenavigation.NavigationActivity;
public class MainActivity extends NavigationActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.launch_screen);
/**
* Reference: https://stackoverflow.com/questions/7944338/resume-last-activity-when-launcher-icon-is-clicked
@ -23,9 +25,4 @@ public class MainActivity extends SplashActivity {
return;
}
}
@Override
public int getSplashLayout() {
return R.layout.launch_screen;
}
}

View file

@ -5,12 +5,26 @@ import com.mattermost.share.RealPathUtil;
import android.app.Activity;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.content.Context;
import android.content.RestrictionsManager;
import android.os.Bundle;
import android.util.Log;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import com.facebook.soloader.SoLoader;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.reactnativedocumentpicker.ReactNativeDocumentPicker;
import com.oblador.keychain.KeychainPackage;
import com.reactlibrary.RNReactNativeDocViewerPackage;
@ -26,10 +40,6 @@ import com.reactnativecommunity.asyncstorage.AsyncStoragePackage;
import com.reactnativecommunity.netinfo.NetInfoPackage;
import com.reactnativecommunity.webview.RNCWebViewPackage;
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import com.imagepicker.ImagePickerPackage;
import com.gnet.bottomsheet.RNBottomSheetPackage;
import com.learnium.RNDeviceInfo.RNDeviceInfo;
@ -37,6 +47,8 @@ import com.psykar.cookiemanager.CookieManagerPackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.BV.LinearGradient.LinearGradientPackage;
import com.reactnativenavigation.NavigationApplication;
import com.reactnativenavigation.react.NavigationReactNativeHost;
import com.reactnativenavigation.react.ReactGateway;
import com.wix.reactnativenotifications.RNNotificationsPackage;
import com.wix.reactnativenotifications.core.notification.INotificationsApplication;
import com.wix.reactnativenotifications.core.notification.IPushNotification;
@ -46,19 +58,9 @@ import com.wix.reactnativenotifications.core.AppLaunchHelper;
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import com.wix.reactnativenotifications.core.JsIOHelper;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import android.support.annotation.Nullable;
import android.util.Log;
public class MainApplication extends NavigationApplication implements INotificationsApplication, INotificationsDrawerApplication {
public NotificationsLifecycleFacade notificationsLifecycleFacade;
public static MainApplication instance;
public Boolean sharedExtensionIsOpened = false;
public long APP_START_TIME;
@ -69,6 +71,19 @@ public class MainApplication extends NavigationApplication implements INotificat
public long PROCESS_PACKAGES_START;
public long PROCESS_PACKAGES_END;
private Bundle mManagedConfig = null;
@Override
protected ReactGateway createReactGateway() {
ReactNativeHost host = new NavigationReactNativeHost(this, isDebug(), createAdditionalReactPackages()) {
@Override
protected String getJSMainModuleName() {
return "index";
}
};
return new ReactGateway(this, isDebug(), host);
}
@Override
public boolean isDebug() {
return BuildConfig.DEBUG;
@ -107,45 +122,30 @@ public class MainApplication extends NavigationApplication implements INotificat
);
}
@Override
public String getJSMainModuleName() {
return "index";
}
@Override
public void onCreate() {
super.onCreate();
instance = this;
registerActivityLifecycleCallbacks(new ManagedActivityLifecycleCallbacks());
// Delete any previous temp files created by the app
File tempFolder = new File(getApplicationContext().getCacheDir(), "mmShare");
RealPathUtil.deleteTempFiles(tempFolder);
Log.i("ReactNative", "Cleaning temp cache " + tempFolder.getAbsolutePath());
// Create an object of the custom facade impl
notificationsLifecycleFacade = NotificationsLifecycleFacade.getInstance();
// Attach it to react-native-navigation
setActivityCallbacks(notificationsLifecycleFacade);
SoLoader.init(this, /* native exopackage */ false);
// Uncomment to listen to react markers for build that has telemetry enabled
// addReactMarkerListener();
}
@Override
public boolean clearHostOnActivityDestroy(Activity activity) {
// This solves the issue where the splash screen does not go away
// after the app is killed by the OS cause of memory or a long time in the background
return false;
}
@Override
public IPushNotification getPushNotification(Context context, Bundle bundle, AppLifecycleFacade defaultFacade, AppLaunchHelper defaultAppLaunchHelper) {
return new CustomPushNotification(
context,
bundle,
notificationsLifecycleFacade, // Instead of defaultFacade!!!
defaultFacade,
defaultAppLaunchHelper,
new JsIOHelper()
);
@ -156,6 +156,51 @@ public class MainApplication extends NavigationApplication implements INotificat
return new CustomPushNotificationDrawer(context, defaultAppLaunchHelper);
}
public ReactContext getRunningReactContext() {
final ReactGateway reactGateway = getReactGateway();
if (reactGateway == null) {
return null;
}
return reactGateway
.getReactNativeHost()
.getReactInstanceManager()
.getCurrentReactContext();
}
public synchronized Bundle loadManagedConfig(Context ctx) {
if (ctx != null) {
RestrictionsManager myRestrictionsMgr =
(RestrictionsManager) ctx.getSystemService(Context.RESTRICTIONS_SERVICE);
mManagedConfig = myRestrictionsMgr.getApplicationRestrictions();
myRestrictionsMgr = null;
if (mManagedConfig!= null && mManagedConfig.size() > 0) {
return mManagedConfig;
}
return null;
}
return null;
}
public synchronized Bundle getManagedConfig() {
if (mManagedConfig!= null && mManagedConfig.size() > 0) {
return mManagedConfig;
}
ReactContext ctx = getRunningReactContext();
if (ctx != null) {
return loadManagedConfig(ctx);
}
return null;
}
private void addReactMarkerListener() {
ReactMarker.addListener(new ReactMarker.MarkerListener() {
@Override
@ -169,7 +214,7 @@ public class MainApplication extends NavigationApplication implements INotificat
PROCESS_PACKAGES_END = System.currentTimeMillis();
} else if (name.toString() == ReactMarkerConstants.CONTENT_APPEARED.toString()) {
CONTENT_APPEARED = System.currentTimeMillis();
ReactContext ctx = getReactGateway().getReactContext();
ReactContext ctx = getRunningReactContext();
if (ctx != null) {
WritableMap map = Arguments.createMap();

View file

@ -0,0 +1,145 @@
package com.mattermost.rnbeta;
import android.os.Bundle;
import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.content.Context;
import android.content.RestrictionsManager;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.util.ArraySet;
import android.util.Log;
import java.util.Set;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
public class ManagedActivityLifecycleCallbacks implements ActivityLifecycleCallbacks {
private static final String TAG = ManagedActivityLifecycleCallbacks.class.getSimpleName();
private final IntentFilter restrictionsFilter =
new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
private final BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context ctx, Intent intent) {
if (ctx != null) {
Bundle managedConfig = MainApplication.instance.loadManagedConfig(ctx);
// Check current configuration settings, change your app's UI and
// functionality as necessary.
Log.i(TAG, "Managed Configuration Changed");
sendConfigChanged(managedConfig);
}
}
};
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
MattermostManagedModule managedModule = MattermostManagedModule.getInstance();
if (managedModule != null && managedModule.isBlurAppScreenEnabled() && activity != null) {
activity.getWindow().setFlags(LayoutParams.FLAG_SECURE,
LayoutParams.FLAG_SECURE);
}
Bundle managedConfig = MainApplication.instance.getManagedConfig();
if (managedConfig != null && activity != null) {
activity.registerReceiver(restrictionsReceiver, restrictionsFilter);
}
}
@Override
public void onActivityResumed(Activity activity) {
ReactContext ctx = MainApplication.instance.getRunningReactContext();
Bundle managedConfig = MainApplication.instance.getManagedConfig();
if (ctx != null) {
Bundle newConfig = MainApplication.instance.loadManagedConfig(ctx);
if (!equalBundles(newConfig, managedConfig)) {
Log.i(TAG, "onResumed Managed Configuration Changed");
sendConfigChanged(newConfig);
}
}
}
@Override
public void onActivityStopped(Activity activity) {
Bundle managedConfig = MainApplication.instance.getManagedConfig();
if (managedConfig != null) {
try {
activity.unregisterReceiver(restrictionsReceiver);
} catch (IllegalArgumentException e) {
// Just ignore this cause the receiver wasn't registered for this activity
}
}
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
private void sendConfigChanged(Bundle config) {
WritableMap result = Arguments.createMap();
if (config != null) {
result = Arguments.fromBundle(config);
}
ReactContext ctx = MainApplication.instance.getRunningReactContext();
if (ctx != null) {
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("managedConfigDidChange", result);
}
}
private boolean equalBundles(Bundle one, Bundle two) {
if (one == null || two == null)
return false;
if(one.size() != two.size())
return false;
Set<String> setOne = new ArraySet<String>();
setOne.addAll(one.keySet());
setOne.addAll(two.keySet());
Object valueOne;
Object valueTwo;
for(String key : setOne) {
if (!one.containsKey(key) || !two.containsKey(key))
return false;
valueOne = one.get(key);
valueTwo = two.get(key);
if(valueOne instanceof Bundle && valueTwo instanceof Bundle &&
!equalBundles((Bundle) valueOne, (Bundle) valueTwo)) {
return false;
}
else if(valueOne == null) {
if(valueTwo != null)
return false;
}
else if(!valueOne.equals(valueTwo))
return false;
}
return true;
}
}

View file

@ -53,7 +53,7 @@ public class MattermostManagedModule extends ReactContextBaseJavaModule {
@ReactMethod
public void getConfig(final Promise promise) {
try {
Bundle config = NotificationsLifecycleFacade.getInstance().getManagedConfig();
Bundle config = MainApplication.instance.getManagedConfig();
if (config != null) {
Object result = Arguments.fromBundle(config);

View file

@ -1,251 +0,0 @@
package com.mattermost.rnbeta;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.RestrictionsManager;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.util.ArraySet;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.content.res.Configuration;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.reactnativenavigation.NavigationApplication;
import com.reactnativenavigation.controllers.ActivityCallbacks;
import com.reactnativenavigation.react.ReactGateway;
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class NotificationsLifecycleFacade extends ActivityCallbacks implements AppLifecycleFacade {
private static final String TAG = NotificationsLifecycleFacade.class.getSimpleName();
private static NotificationsLifecycleFacade instance;
private Bundle managedConfig = null;
private Activity mVisibleActivity;
private Set<AppVisibilityListener> mListeners = new CopyOnWriteArraySet<>();
private final IntentFilter restrictionsFilter =
new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
private final BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
if (context != null) {
// Get the current configuration bundle
RestrictionsManager myRestrictionsMgr =
(RestrictionsManager) context
.getSystemService(Context.RESTRICTIONS_SERVICE);
managedConfig = myRestrictionsMgr.getApplicationRestrictions();
// Check current configuration settings, change your app's UI and
// functionality as necessary.
Log.i("ReactNative", "Managed Configuration Changed");
sendConfigChanged(managedConfig);
}
}
};
public static NotificationsLifecycleFacade getInstance() {
if (instance == null) {
instance = new NotificationsLifecycleFacade();
}
return instance;
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
MattermostManagedModule managedModule = MattermostManagedModule.getInstance();
if (managedModule != null && managedModule.isBlurAppScreenEnabled() && activity != null) {
activity.getWindow().setFlags(LayoutParams.FLAG_SECURE,
LayoutParams.FLAG_SECURE);
}
if (managedConfig != null && managedConfig.size() > 0 && activity != null) {
activity.registerReceiver(restrictionsReceiver, restrictionsFilter);
}
if (activity != null) {
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
}
@Override
public void onActivityResumed(Activity activity) {
switchToVisible(activity);
ReactContext ctx = getRunningReactContext();
if (managedConfig != null && managedConfig.size() > 0 && ctx != null) {
RestrictionsManager myRestrictionsMgr =
(RestrictionsManager) ctx
.getSystemService(Context.RESTRICTIONS_SERVICE);
Bundle newConfig = myRestrictionsMgr.getApplicationRestrictions();
if (!equalBundles(newConfig ,managedConfig)) {
Log.i("ReactNative", "onResumed Managed Configuration Changed");
managedConfig = newConfig;
sendConfigChanged(managedConfig);
}
}
}
@Override
public void onActivityPaused(Activity activity) {
switchToInvisible(activity);
}
@Override
public void onActivityStopped(Activity activity) {
switchToInvisible(activity);
if (managedConfig != null && managedConfig.size() > 0) {
try {
activity.unregisterReceiver(restrictionsReceiver);
} catch (IllegalArgumentException e) {
// Just ignore this cause the receiver wasn't registered for this activity
}
}
}
@Override
public void onActivityDestroyed(Activity activity) {
switchToInvisible(activity);
}
@Override
public boolean isReactInitialized() {
return NavigationApplication.instance.isReactContextInitialized();
}
@Override
public ReactContext getRunningReactContext() {
final ReactGateway reactGateway = NavigationApplication.instance.getReactGateway();
if (reactGateway == null || !reactGateway.isInitialized()) {
return null;
}
return reactGateway.getReactContext();
}
@Override
public boolean isAppVisible() {
return mVisibleActivity != null;
}
@Override
public synchronized void addVisibilityListener(AppVisibilityListener listener) {
mListeners.add(listener);
}
@Override
public synchronized void removeVisibilityListener(AppVisibilityListener listener) {
mListeners.remove(listener);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mVisibleActivity != null) {
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
mVisibleActivity.sendBroadcast(intent);
}
}
private synchronized void switchToVisible(Activity activity) {
if (mVisibleActivity == null) {
mVisibleActivity = activity;
Log.v(TAG, "Activity is now visible ("+activity+")");
for (AppVisibilityListener listener : mListeners) {
listener.onAppVisible();
}
}
}
private synchronized void switchToInvisible(Activity activity) {
if (mVisibleActivity == activity) {
mVisibleActivity = null;
Log.v(TAG, "Activity is now NOT visible ("+activity+")");
for (AppVisibilityListener listener : mListeners) {
listener.onAppNotVisible();
}
}
}
public synchronized void LoadManagedConfig(ReactContext ctx) {
if (ctx != null) {
RestrictionsManager myRestrictionsMgr =
(RestrictionsManager) ctx
.getSystemService(Context.RESTRICTIONS_SERVICE);
managedConfig = myRestrictionsMgr.getApplicationRestrictions();
myRestrictionsMgr = null;
}
}
public synchronized Bundle getManagedConfig() {
if (managedConfig!= null && managedConfig.size() > 0) {
return managedConfig;
}
ReactContext ctx = getRunningReactContext();
if (ctx != null) {
LoadManagedConfig(ctx);
return managedConfig;
}
return null;
}
public void sendConfigChanged(Bundle config) {
Object result = Arguments.fromBundle(config);
ReactContext ctx = getRunningReactContext();
if (ctx != null) {
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).
emit("managedConfigDidChange", result);
}
}
private boolean equalBundles(Bundle one, Bundle two) {
if (one == null || two == null)
return false;
if(one.size() != two.size())
return false;
Set<String> setOne = new ArraySet<String>();
setOne.addAll(one.keySet());
setOne.addAll(two.keySet());
Object valueOne;
Object valueTwo;
for(String key : setOne) {
if (!one.containsKey(key) || !two.containsKey(key))
return false;
valueOne = one.get(key);
valueTwo = two.get(key);
if(valueOne instanceof Bundle && valueTwo instanceof Bundle &&
!equalBundles((Bundle) valueOne, (Bundle) valueTwo)) {
return false;
}
else if(valueOne == null) {
if(valueTwo != null)
return false;
}
else if(!valueOne.equals(valueTwo))
return false;
}
return true;
}
}

View file

@ -11,6 +11,8 @@ buildscript {
repositories {
jcenter()
google()
mavenLocal()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
@ -37,6 +39,7 @@ subprojects {
allprojects {
repositories {
google()
mavenCentral()
mavenLocal()
jcenter()
maven {
@ -47,5 +50,8 @@ allprojects {
// Local Maven repo containing AARs with JSC library built for Android
url "$rootDir/../node_modules/jsc-android/dist"
}
maven {
url "https://jitpack.io"
}
}
}

View file

@ -22,7 +22,7 @@ project(':jail-monkey').projectDir = new File(rootProject.projectDir, '../node_m
include ':react-native-local-auth'
project(':react-native-local-auth').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-local-auth/android')
include ':react-native-navigation'
project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/android/app/')
project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/lib/android/app/')
include ':react-native-image-picker'
project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
include ':react-native-bottom-sheet'

361
app/actions/navigation.js Normal file
View file

@ -0,0 +1,361 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Platform} from 'react-native';
import {Navigation} from 'react-native-navigation';
import merge from 'deepmerge';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import EphemeralStore from 'app/store/ephemeral_store';
export function resetToChannel(passProps = {}) {
return (dispatch, getState) => {
const theme = getTheme(getState());
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
name: 'Channel',
passProps,
options: {
layout: {
backgroundColor: 'transparent',
},
statusBar: {
visible: true,
},
topBar: {
visible: false,
height: 0,
backButton: {
color: theme.sidebarHeaderTextColor,
title: '',
},
background: {
color: theme.sidebarHeaderBg,
},
title: {
color: theme.sidebarHeaderTextColor,
},
},
},
},
}],
},
},
});
};
}
export function resetToSelectServer(allowOtherServers) {
return (dispatch, getState) => {
const theme = getTheme(getState());
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
name: 'SelectServer',
passProps: {
allowOtherServers,
},
options: {
statusBar: {
visible: true,
},
topBar: {
backButton: {
color: theme.sidebarHeaderTextColor,
title: '',
},
background: {
color: theme.sidebarHeaderBg,
},
visible: false,
height: 0,
},
},
},
}],
},
},
});
};
}
export function resetToTeams(name, title, passProps = {}, options = {}) {
return (dispatch, getState) => {
const theme = getTheme(getState());
const defaultOptions = {
layout: {
backgroundColor: theme.centerChannelBg,
},
statusBar: {
visible: true,
},
topBar: {
visible: true,
title: {
color: theme.sidebarHeaderTextColor,
text: title,
},
backButton: {
color: theme.sidebarHeaderTextColor,
title: '',
},
background: {
color: theme.sidebarHeaderBg,
},
},
};
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
name,
passProps,
options: merge(defaultOptions, options),
},
}],
},
},
});
};
}
export function goToScreen(name, title, passProps = {}, options = {}) {
return (dispatch, getState) => {
const state = getState();
const componentId = EphemeralStore.getTopComponentId();
const theme = getTheme(state);
const defaultOptions = {
layout: {
backgroundColor: theme.centerChannelBg,
},
topBar: {
animate: true,
visible: true,
backButton: {
color: theme.sidebarHeaderTextColor,
title: '',
},
background: {
color: theme.sidebarHeaderBg,
},
title: {
color: theme.sidebarHeaderTextColor,
text: title,
},
},
};
Navigation.push(componentId, {
component: {
name,
passProps,
options: merge(defaultOptions, options),
},
});
};
}
export function popTopScreen() {
return () => {
const componentId = EphemeralStore.getTopComponentId();
Navigation.pop(componentId);
};
}
export function popToRoot() {
return () => {
const componentId = EphemeralStore.getTopComponentId();
Navigation.popToRoot(componentId).catch(() => {
// RNN returns a promise rejection if there are no screens
// atop the root screen to pop. We'll do nothing in this
// case but we will catch the rejection here so that the
// caller doesn't have to.
});
};
}
export function showModal(name, title, passProps = {}, options = {}) {
return (dispatch, getState) => {
const theme = getTheme(getState());
const defaultOptions = {
layout: {
backgroundColor: theme.centerChannelBg,
},
statusBar: {
visible: true,
},
topBar: {
animate: true,
visible: true,
backButton: {
color: theme.sidebarHeaderTextColor,
title: '',
},
background: {
color: theme.sidebarHeaderBg,
},
title: {
color: theme.sidebarHeaderTextColor,
text: title,
},
leftButtonColor: theme.sidebarHeaderTextColor,
rightButtonColor: theme.sidebarHeaderTextColor,
},
};
Navigation.showModal({
stack: {
children: [{
component: {
name,
passProps,
options: merge(defaultOptions, options),
},
}],
},
});
};
}
export function showModalOverCurrentContext(name, passProps = {}, options = {}) {
return (dispatch) => {
const title = '';
const animationsEnabled = (Platform.OS === 'android').toString();
const defaultOptions = {
modalPresentationStyle: 'overCurrentContext',
layout: {
backgroundColor: 'transparent',
},
topBar: {
visible: false,
height: 0,
},
animations: {
showModal: {
enabled: animationsEnabled,
alpha: {
from: 0,
to: 1,
duration: 250,
},
},
dismissModal: {
enabled: animationsEnabled,
alpha: {
from: 1,
to: 0,
duration: 250,
},
},
},
};
const mergeOptions = merge(defaultOptions, options);
dispatch(showModal(name, title, passProps, mergeOptions));
};
}
export function showSearchModal(initialValue = '') {
return (dispatch) => {
const name = 'Search';
const title = '';
const passProps = {initialValue};
const options = {
topBar: {
visible: false,
height: 0,
},
};
dispatch(showModal(name, title, passProps, options));
};
}
export function dismissModal(options = {}) {
return () => {
const componentId = EphemeralStore.getTopComponentId();
Navigation.dismissModal(componentId, options);
};
}
export function dismissAllModals(options = {}) {
return () => {
Navigation.dismissAllModals(options).catch(() => {
// RNN returns a promise rejection if there are no modals to
// dismiss. We'll do nothing in this case but we will catch
// the rejection here so that the caller doesn't have to.
});
};
}
export function peek(name, passProps = {}, options = {}) {
return () => {
const componentId = EphemeralStore.getTopComponentId();
const defaultOptions = {
preview: {
commit: false,
},
};
Navigation.push(componentId, {
component: {
name,
passProps,
options: merge(defaultOptions, options),
},
});
};
}
export function setButtons(componentId, buttons = {leftButtons: [], rightButtons: []}) {
return () => {
Navigation.mergeOptions(componentId, {
topBar: {
...buttons,
},
});
};
}
export function showOverlay(name, passProps, options = {}) {
return () => {
const defaultOptions = {
overlay: {
interceptTouchOutside: false,
},
};
Navigation.showOverlay({
component: {
name,
passProps,
options: merge(defaultOptions, options),
},
});
};
}
export function dismissOverlay(componentId) {
return () => {
return Navigation.dismissOverlay(componentId).catch(() => {
// RNN returns a promise rejection if there is no modal with
// this componentId to dismiss. We'll do nothing in this case
// but we will catch the rejection here so that the caller
// doesn't have to.
});
};
}

View file

@ -1,8 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {ViewTypes} from 'app/constants';
export function handleSearchDraftChanged(text) {
@ -13,25 +11,3 @@ export function handleSearchDraftChanged(text) {
}, getState);
};
}
export function showSearchModal(navigator, initialValue = '') {
return (dispatch, getState) => {
const theme = getTheme(getState());
const options = {
screen: 'Search',
animated: true,
backButtonTitle: '',
overrideBackPress: true,
passProps: {
initialValue,
},
navigatorStyle: {
navBarHidden: true,
screenBackgroundColor: theme.centerChannelBg,
},
};
navigator.showModal(options);
};
}

View file

@ -1,29 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`profile_picture_button should match snapshot 1`] = `
<AttachmentButton
<Connect(AttachmentButton)
blurTextBox={[MockFunction]}
browseFileTypes="public.item"
canBrowseFiles={true}
canBrowsePhotoLibrary={true}
canBrowseVideoLibrary={true}
canTakePhoto={true}
canTakeVideo={true}
extraOptions={
Array [
null,
]
}
maxFileCount={5}
maxFileSize={20971520}
navigator={
Object {
"dismissModal": [MockFunction],
"push": [MockFunction],
"setButtons": [MockFunction],
"setOnNavigatorEvent": [MockFunction],
}
}
theme={
Object {
"awayIndicator": "#ffbc42",
@ -53,6 +38,5 @@ exports[`profile_picture_button should match snapshot 1`] = `
}
}
uploadFiles={[MockFunction]}
validMimeTypes={Array []}
/>
`;

View file

@ -18,12 +18,14 @@ const {View: AnimatedView} = Animated;
export default class AnnouncementBanner extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
goToScreen: PropTypes.func.isRequired,
}).isRequired,
bannerColor: PropTypes.string,
bannerDismissed: PropTypes.bool,
bannerEnabled: PropTypes.bool,
bannerText: PropTypes.string,
bannerTextColor: PropTypes.string,
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
};
@ -52,23 +54,16 @@ export default class AnnouncementBanner extends PureComponent {
}
handlePress = () => {
const {navigator, theme} = this.props;
const {actions} = this.props;
const {intl} = this.context;
navigator.push({
screen: 'ExpandedAnnouncementBanner',
title: this.context.intl.formatMessage({
id: 'mobile.announcement_banner.title',
defaultMessage: 'Announcement',
}),
animated: true,
backButtonTitle: '',
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
const screen = 'ExpandedAnnouncementBanner';
const title = intl.formatMessage({
id: 'mobile.announcement_banner.title',
defaultMessage: 'Announcement',
});
actions.goToScreen(screen, title);
};
toggleBanner = (show = true) => {

View file

@ -12,12 +12,14 @@ jest.useFakeTimers();
describe('AnnouncementBanner', () => {
const baseProps = {
actions: {
goToScreen: jest.fn(),
},
bannerColor: '#ddd',
bannerDismissed: false,
bannerEnabled: true,
bannerText: 'Banner Text',
bannerTextColor: '#fff',
navigator: {},
theme: Preferences.THEMES.default,
};

View file

@ -1,11 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {goToScreen} from 'app/actions/navigation';
import AnnouncementBanner from './announcement_banner';
function mapStateToProps(state) {
@ -23,4 +26,12 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps)(AnnouncementBanner);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
goToScreen,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AnnouncementBanner);

View file

@ -3,7 +3,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Clipboard, Platform, Text} from 'react-native';
import {Clipboard, Text} from 'react-native';
import {intlShape} from 'react-intl';
import {displayUsername} from 'mattermost-redux/utils/user_utils';
@ -14,10 +14,12 @@ import BottomSheet from 'app/utils/bottom_sheet';
export default class AtMention extends React.PureComponent {
static propTypes = {
actions: PropTypes.shape({
goToScreen: PropTypes.func.isRequired,
}).isRequired,
isSearchResult: PropTypes.bool,
mentionName: PropTypes.string.isRequired,
mentionStyle: CustomPropTypes.Style,
navigator: PropTypes.object.isRequired,
onPostPress: PropTypes.func,
textStyle: CustomPropTypes.Style,
teammateNameDisplay: PropTypes.string,
@ -48,29 +50,15 @@ export default class AtMention extends React.PureComponent {
}
goToUserProfile = () => {
const {navigator, theme} = this.props;
const {actions} = this.props;
const {intl} = this.context;
const options = {
screen: 'UserProfile',
title: intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}),
animated: true,
backButtonTitle: '',
passProps: {
userId: this.state.user.id,
},
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
const screen = 'UserProfile';
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
const passProps = {
userId: this.state.user.id,
};
if (Platform.OS === 'ios') {
navigator.push(options);
} else {
navigator.showModal(options);
}
actions.goToScreen(screen, title, passProps);
};
getUserDetailsFromMentionName(props) {

View file

@ -1,12 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getUsersByUsername} from 'mattermost-redux/selectors/entities/users';
import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {goToScreen} from 'app/actions/navigation';
import AtMention from './at_mention';
function mapStateToProps(state) {
@ -17,4 +20,12 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps)(AtMention);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
goToScreen,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AtMention);

View file

@ -27,6 +27,9 @@ const ShareExtension = NativeModules.MattermostShare;
export default class AttachmentButton extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
showModalOverCurrentContext: PropTypes.func.isRequired,
}).isRequired,
blurTextBox: PropTypes.func.isRequired,
browseFileTypes: PropTypes.string,
validMimeTypes: PropTypes.array,
@ -39,7 +42,6 @@ export default class AttachmentButton extends PureComponent {
fileCount: PropTypes.number,
maxFileCount: PropTypes.number.isRequired,
maxFileSize: PropTypes.number.isRequired,
navigator: PropTypes.object.isRequired,
onShowFileMaxWarning: PropTypes.func,
onShowFileSizeWarning: PropTypes.func,
onShowUnsupportedMimeTypeWarning: PropTypes.func,
@ -340,21 +342,6 @@ export default class AttachmentButton extends PureComponent {
}
};
handleFileAttachmentOption = (action) => {
this.props.navigator.dismissModal({
animationType: 'none',
});
// Have to wait to launch the library attachment action.
// If we call the action after dismissModal with no delay then the
// Wix navigator will dismiss the library attachment modal as well.
setTimeout(() => {
if (typeof action === 'function') {
action();
}
}, 100);
};
showFileAttachmentOptions = () => {
const {
canBrowseFiles,
@ -366,6 +353,7 @@ export default class AttachmentButton extends PureComponent {
maxFileCount,
onShowFileMaxWarning,
extraOptions,
actions,
} = this.props;
if (fileCount === maxFileCount) {
@ -378,7 +366,7 @@ export default class AttachmentButton extends PureComponent {
if (canTakePhoto) {
items.push({
action: () => this.handleFileAttachmentOption(this.attachPhotoFromCamera),
action: this.attachPhotoFromCamera,
text: {
id: t('mobile.file_upload.camera_photo'),
defaultMessage: 'Take Photo',
@ -389,7 +377,7 @@ export default class AttachmentButton extends PureComponent {
if (canTakeVideo) {
items.push({
action: () => this.handleFileAttachmentOption(this.attachVideoFromCamera),
action: this.attachVideoFromCamera,
text: {
id: t('mobile.file_upload.camera_video'),
defaultMessage: 'Take Video',
@ -400,7 +388,7 @@ export default class AttachmentButton extends PureComponent {
if (canBrowsePhotoLibrary) {
items.push({
action: () => this.handleFileAttachmentOption(this.attachFileFromLibrary),
action: this.attachFileFromLibrary,
text: {
id: t('mobile.file_upload.library'),
defaultMessage: 'Photo Library',
@ -411,7 +399,7 @@ export default class AttachmentButton extends PureComponent {
if (canBrowseVideoLibrary && Platform.OS === 'android') {
items.push({
action: () => this.handleFileAttachmentOption(this.attachVideoFromLibraryAndroid),
action: this.attachVideoFromLibraryAndroid,
text: {
id: t('mobile.file_upload.video'),
defaultMessage: 'Video Library',
@ -422,7 +410,7 @@ export default class AttachmentButton extends PureComponent {
if (canBrowseFiles) {
items.push({
action: () => this.handleFileAttachmentOption(this.attachFileFromFiles),
action: this.attachFileFromFiles,
text: {
id: t('mobile.file_upload.browse'),
defaultMessage: 'Browse Files',
@ -439,21 +427,7 @@ export default class AttachmentButton extends PureComponent {
});
}
this.props.navigator.showModal({
screen: 'OptionsModal',
title: '',
animationType: 'none',
passProps: {
items,
},
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
});
actions.showModalOverCurrentContext('OptionsModal', {items});
};
render() {

View file

@ -13,8 +13,10 @@ jest.mock('react-intl');
describe('AttachmentButton', () => {
const baseProps = {
actions: {
showModalOverCurrentContext: jest.fn(),
},
theme: Preferences.THEMES.default,
navigator: {},
blurTextBox: jest.fn(),
maxFileSize: 10,
uploadFiles: jest.fn(),

View file

@ -0,0 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {showModalOverCurrentContext} from 'app/actions/navigation';
import AttachmentButton from './attachment_button';
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
showModalOverCurrentContext,
}, dispatch),
};
}
export default connect(null, mapDispatchToProps)(AttachmentButton);

View file

@ -18,6 +18,7 @@ export default class AutocompleteSelector extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
setAutocompleteSelector: PropTypes.func.isRequired,
goToScreen: PropTypes.func.isRequired,
}).isRequired,
label: PropTypes.string,
placeholder: PropTypes.string.isRequired,
@ -28,7 +29,6 @@ export default class AutocompleteSelector extends PureComponent {
showRequiredAsterisk: PropTypes.bool,
teammateNameDisplay: PropTypes.string,
theme: PropTypes.object.isRequired,
navigator: PropTypes.object,
onSelected: PropTypes.func,
helpText: PropTypes.node,
errorText: PropTypes.node,
@ -96,22 +96,12 @@ export default class AutocompleteSelector extends PureComponent {
goToSelectorScreen = preventDoubleTap(() => {
const {formatMessage} = this.context.intl;
const {navigator, theme, actions, dataSource, options, placeholder} = this.props;
const {actions, dataSource, options, placeholder} = this.props;
const screen = 'SelectorScreen';
const title = placeholder || formatMessage({id: 'mobile.action_menu.select', defaultMessage: 'Select an option'});
actions.setAutocompleteSelector(dataSource, this.handleSelect, options);
navigator.push({
backButtonTitle: '',
screen: 'SelectorScreen',
title: placeholder || formatMessage({id: 'mobile.action_menu.select', defaultMessage: 'Select an option'}),
animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
});
actions.goToScreen(screen, title);
});
render() {

View file

@ -6,6 +6,7 @@ import {connect} from 'react-redux';
import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {goToScreen} from 'app/actions/navigation';
import {setAutocompleteSelector} from 'app/actions/views/post';
import AutocompleteSelector from './autocomplete_selector';
@ -21,6 +22,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
setAutocompleteSelector,
goToScreen,
}, dispatch),
};
}

View file

@ -4,7 +4,6 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Platform,
Text,
TouchableOpacity,
View,
@ -21,11 +20,13 @@ import {t} from 'app/utils/i18n';
class ChannelIntro extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
goToScreen: PropTypes.func.isRequired,
}).isRequired,
creator: PropTypes.object,
currentChannel: PropTypes.object.isRequired,
currentChannelMembers: PropTypes.array.isRequired,
intl: intlShape.isRequired,
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
};
@ -34,28 +35,14 @@ class ChannelIntro extends PureComponent {
};
goToUserProfile = (userId) => {
const {intl, navigator, theme} = this.props;
const options = {
screen: 'UserProfile',
title: intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}),
animated: true,
backButtonTitle: '',
passProps: {
userId,
},
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
const {actions, intl} = this.props;
const screen = 'UserProfile';
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
const passProps = {
userId,
};
if (Platform.OS === 'ios') {
navigator.push(options);
} else {
navigator.showModal(options);
}
actions.goToScreen(screen, title, passProps);
};
getDisplayName = (member) => {

View file

@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {createSelector} from 'reselect';
@ -10,6 +11,7 @@ import {getCurrentUserId, getUser, makeGetProfilesInChannel} from 'mattermost-re
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {goToScreen} from 'app/actions/navigation';
import {getChannelMembersForDm} from 'app/selectors/channel';
import ChannelIntro from './channel_intro';
@ -52,4 +54,12 @@ function makeMapStateToProps() {
};
}
export default connect(makeMapStateToProps)(ChannelIntro);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
goToScreen,
}, dispatch),
};
}
export default connect(makeMapStateToProps, mapDispatchToProps)(ChannelIntro);

View file

@ -27,6 +27,8 @@ export default class ClientUpgradeListener extends PureComponent {
actions: PropTypes.shape({
logError: PropTypes.func.isRequired,
setLastUpgradeCheck: PropTypes.func.isRequired,
showModal: PropTypes.func.isRequired,
dismissModal: PropTypes.func.isRequired,
}).isRequired,
currentVersion: PropTypes.string,
downloadLink: PropTypes.string,
@ -35,7 +37,6 @@ export default class ClientUpgradeListener extends PureComponent {
lastUpgradeCheck: PropTypes.number,
latestVersion: PropTypes.string,
minVersion: PropTypes.string,
navigator: PropTypes.object,
theme: PropTypes.object.isRequired,
};
@ -140,27 +141,26 @@ export default class ClientUpgradeListener extends PureComponent {
};
handleLearnMore = () => {
const {actions} = this.props;
const {intl} = this.context;
this.props.navigator.dismissModal({animationType: 'none'});
this.props.navigator.showModal({
screen: 'ClientUpgrade',
title: intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Upgrade App'}),
navigatorStyle: {
navBarHidden: false,
statusBarHidden: false,
statusBarHideWithNavBar: false,
},
navigatorButtons: {
actions.dismissModal();
const screen = 'ClientUpgrade';
const title = intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Upgrade App'});
const passProps = {
upgradeType: this.state.upgradeType,
};
const options = {
topBar: {
leftButtons: [{
id: 'close-upgrade',
icon: this.closeButton,
}],
},
passProps: {
upgradeType: this.state.upgradeType,
},
});
};
actions.showModal(screen, title, passProps, options);
this.toggleUpgradeMessage(false);
};

View file

@ -4,11 +4,12 @@ import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {logError} from 'mattermost-redux/actions/errors';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {showModal, dismissModal} from 'app/actions/navigation';
import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade';
import getClientUpgrade from 'app/selectors/client_upgrade';
import {isLandscape} from 'app/selectors/device';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import ClientUpgradeListener from './client_upgrade_listener';
@ -32,6 +33,8 @@ function mapDispatchToProps(dispatch) {
actions: bindActionCreators({
logError,
setLastUpgradeCheck,
showModal,
dismissModal,
}, dispatch),
};
}

View file

@ -177,7 +177,6 @@ export default class CombinedSystemMessage extends React.PureComponent {
currentUserId: PropTypes.string.isRequired,
currentUsername: PropTypes.string.isRequired,
messageData: PropTypes.array.isRequired,
navigator: PropTypes.object.isRequired,
showJoinLeave: PropTypes.bool.isRequired,
textStyles: PropTypes.object,
theme: PropTypes.object.isRequired,
@ -266,7 +265,6 @@ export default class CombinedSystemMessage extends React.PureComponent {
const {
currentUserId,
currentUsername,
navigator,
textStyles,
theme,
} = this.props;
@ -285,7 +283,6 @@ export default class CombinedSystemMessage extends React.PureComponent {
<LastUsers
actor={actor}
expandedLocale={postTypeMessage[postType].many_expanded}
navigator={navigator}
postType={postType}
style={style}
textStyles={textStyles}
@ -314,7 +311,6 @@ export default class CombinedSystemMessage extends React.PureComponent {
return (
<Markdown
baseTextStyle={style.baseText}
navigator={navigator}
textStyles={textStyles}
value={formattedMessage}
/>

View file

@ -53,7 +53,6 @@ export default class LastUsers extends React.PureComponent {
static propTypes = {
actor: PropTypes.string,
expandedLocale: PropTypes.object.isRequired,
navigator: PropTypes.object.isRequired,
postType: PropTypes.string.isRequired,
style: PropTypes.object.isRequired,
textStyles: PropTypes.object,
@ -88,7 +87,6 @@ export default class LastUsers extends React.PureComponent {
const {
actor,
expandedLocale,
navigator,
style,
textStyles,
usernames,
@ -106,7 +104,6 @@ export default class LastUsers extends React.PureComponent {
return (
<Markdown
baseTextStyle={style.baseText}
navigator={navigator}
textStyles={textStyles}
value={formattedMessage}
/>
@ -116,7 +113,6 @@ export default class LastUsers extends React.PureComponent {
renderCollapsedView = () => {
const {
actor,
navigator,
postType,
style,
textStyles,
@ -134,7 +130,6 @@ export default class LastUsers extends React.PureComponent {
defaultMessage={'{firstUser} and '}
values={{firstUser}}
baseTextStyle={style.baseText}
navigator={navigator}
style={style.baseText}
textStyles={textStyles}
theme={theme}
@ -155,7 +150,6 @@ export default class LastUsers extends React.PureComponent {
defaultMessage={typeMessage[postType].defaultMessage}
values={{actor}}
baseTextStyle={style.baseText}
navigator={navigator}
style={style.baseText}
textStyles={textStyles}
theme={theme}

View file

@ -0,0 +1,415 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Platform,
TouchableWithoutFeedback,
View,
Text,
findNodeHandle,
} from 'react-native';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
import Loading from 'app/components/loading';
import StatusBar from 'app/components/status_bar';
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {General} from 'mattermost-redux/constants';
import {getShortenedURL} from 'app/utils/url';
import {t} from 'app/utils/i18n';
export default class EditChannelInfo extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
dismissModal: PropTypes.func.isRequired,
popTopScreen: PropTypes.func.isRequired,
}),
theme: PropTypes.object.isRequired,
deviceWidth: PropTypes.number.isRequired,
deviceHeight: PropTypes.number.isRequired,
channelType: PropTypes.string,
enableRightButton: PropTypes.func,
saving: PropTypes.bool.isRequired,
editing: PropTypes.bool,
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
displayName: PropTypes.string,
currentTeamUrl: PropTypes.string,
channelURL: PropTypes.string,
purpose: PropTypes.string,
header: PropTypes.string,
onDisplayNameChange: PropTypes.func,
onChannelURLChange: PropTypes.func,
onPurposeChange: PropTypes.func,
onHeaderChange: PropTypes.func,
oldDisplayName: PropTypes.string,
oldChannelURL: PropTypes.string,
oldHeader: PropTypes.string,
oldPurpose: PropTypes.string,
};
static defaultProps = {
editing: false,
};
constructor(props) {
super(props);
this.nameInput = React.createRef();
this.urlInput = React.createRef();
this.purposeInput = React.createRef();
this.headerInput = React.createRef();
this.lastText = React.createRef();
this.scroll = React.createRef();
}
blur = () => {
if (this.nameInput?.current) {
this.nameInput.current.blur();
}
// TODO: uncomment below once the channel URL field is added
// if (this.urlInput?.current) {
// this.urlInput.current.blur();
// }
if (this.purposeInput?.current) {
this.purposeInput.current.blur();
}
if (this.headerInput?.current) {
this.headerInput.current.blur();
}
if (this.scroll?.current) {
this.scroll.current.scrollToPosition(0, 0, true);
}
};
close = (goBack = false) => {
const {actions} = this.props;
if (goBack) {
actions.popTopScreen();
} else {
actions.dismissModal();
}
};
canUpdate = (displayName, channelURL, purpose, header) => {
const {
oldDisplayName,
oldChannelURL,
oldPurpose,
oldHeader,
} = this.props;
return displayName !== oldDisplayName || channelURL !== oldChannelURL ||
purpose !== oldPurpose || header !== oldHeader;
};
enableRightButton = (enable = false) => {
this.props.enableRightButton(enable);
};
onDisplayNameChangeText = (displayName) => {
const {editing, onDisplayNameChange} = this.props;
onDisplayNameChange(displayName);
if (editing) {
const {channelURL, purpose, header} = this.props;
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
this.enableRightButton(canUpdate);
return;
}
const displayNameExists = displayName && displayName.length >= 2;
this.props.enableRightButton(displayNameExists);
};
onDisplayURLChangeText = (channelURL) => {
const {editing, onChannelURLChange} = this.props;
onChannelURLChange(channelURL);
if (editing) {
const {displayName, purpose, header} = this.props;
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
this.enableRightButton(canUpdate);
}
};
onPurposeChangeText = (purpose) => {
const {editing, onPurposeChange} = this.props;
onPurposeChange(purpose);
if (editing) {
const {displayName, channelURL, header} = this.props;
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
this.enableRightButton(canUpdate);
}
};
onHeaderChangeText = (header) => {
const {editing, onHeaderChange} = this.props;
onHeaderChange(header);
if (editing) {
const {displayName, channelURL, purpose} = this.props;
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
this.enableRightButton(canUpdate);
}
};
scrollToEnd = () => {
if (this.scroll?.current && this.lastText?.current) {
this.scroll.current.scrollToFocusedInput(findNodeHandle(this.lastText.current));
}
};
render() {
const {
theme,
editing,
channelType,
currentTeamUrl,
deviceWidth,
deviceHeight,
displayName,
channelURL,
header,
purpose,
} = this.props;
const {error, saving} = this.props;
const fullUrl = currentTeamUrl + '/channels';
const shortUrl = getShortenedURL(fullUrl, 35);
const style = getStyleSheet(theme);
const displayHeaderOnly = channelType === General.DM_CHANNEL ||
channelType === General.GM_CHANNEL;
if (saving) {
return (
<View style={style.container}>
<StatusBar/>
<Loading/>
</View>
);
}
let displayError;
if (error) {
displayError = (
<View style={[style.errorContainer, {width: deviceWidth}]}>
<View style={style.errorWrapper}>
<ErrorText error={error}/>
</View>
</View>
);
}
return (
<View style={style.container}>
<StatusBar/>
<KeyboardAwareScrollView
ref={this.scroll}
style={style.container}
>
{displayError}
<TouchableWithoutFeedback onPress={this.blur}>
<View style={[style.scrollView, {height: deviceHeight + (Platform.OS === 'android' ? 200 : 0)}]}>
{!displayHeaderOnly && (
<View>
<View>
<FormattedText
style={style.title}
id='channel_modal.name'
defaultMessage='Name'
/>
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.nameInput}
value={displayName}
onChangeText={this.onDisplayNameChangeText}
style={style.input}
autoCapitalize='none'
autoCorrect={false}
placeholder={{id: t('channel_modal.nameEx'), defaultMessage: 'E.g.: "Bugs", "Marketing", "客户支持"'}}
placeholderTextColor={changeOpacity('#000', 0.5)}
underlineColorAndroid='transparent'
disableFullscreenUI={true}
/>
</View>
</View>
)}
{/*TODO: Hide channel url field until it's added to CreateChannel */}
{false && editing && !displayHeaderOnly && (
<View>
<View style={style.titleContainer30}>
<FormattedText
style={style.title}
id='rename_channel.url'
defaultMessage='URL'
/>
<Text style={style.optional}>
{shortUrl}
</Text>
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.urlInput}
value={channelURL}
onChangeText={this.onDisplayURLChangeText}
style={style.input}
autoCapitalize='none'
autoCorrect={false}
placeholder={{id: t('rename_channel.handleHolder'), defaultMessage: 'lowercase alphanumeric characters'}}
placeholderTextColor={changeOpacity('#000', 0.5)}
underlineColorAndroid='transparent'
disableFullscreenUI={true}
/>
</View>
</View>
)}
{!displayHeaderOnly && (
<View>
<View style={style.titleContainer30}>
<FormattedText
style={style.title}
id='channel_modal.purpose'
defaultMessage='Purpose'
/>
<FormattedText
style={style.optional}
id='channel_modal.optional'
defaultMessage='(optional)'
/>
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.purposeInput}
value={purpose}
onChangeText={this.onPurposeChangeText}
style={[style.input, {height: 110}]}
autoCapitalize='none'
autoCorrect={false}
placeholder={{id: t('channel_modal.purposeEx'), defaultMessage: 'E.g.: "A channel to file bugs and improvements"'}}
placeholderTextColor={changeOpacity('#000', 0.5)}
multiline={true}
blurOnSubmit={false}
textAlignVertical='top'
underlineColorAndroid='transparent'
disableFullscreenUI={true}
/>
</View>
<View>
<FormattedText
style={style.helpText}
id='channel_modal.descriptionHelp'
defaultMessage='Describe how this channel should be used.'
/>
</View>
</View>
)}
<View style={style.titleContainer15}>
<FormattedText
style={style.title}
id='channel_modal.header'
defaultMessage='Header'
/>
<FormattedText
style={style.optional}
id='channel_modal.optional'
defaultMessage='(optional)'
/>
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.headerInput}
value={header}
onChangeText={this.onHeaderChangeText}
style={[style.input, {height: 110}]}
autoCapitalize='none'
autoCorrect={false}
placeholder={{id: t('channel_modal.headerEx'), defaultMessage: 'E.g.: "[Link Title](http://example.com)"'}}
placeholderTextColor={changeOpacity('#000', 0.5)}
multiline={true}
blurOnSubmit={false}
onFocus={this.scrollToEnd}
textAlignVertical='top'
underlineColorAndroid='transparent'
disableFullscreenUI={true}
/>
</View>
<View ref={this.lastText}>
<FormattedText
style={style.helpText}
id='channel_modal.headerHelp'
defaultMessage={'Set text that will appear in the header of the channel beside the channel name. For example, include frequently used links by typing [Link Title](http://example.com).'}
/>
</View>
</View>
</TouchableWithoutFeedback>
</KeyboardAwareScrollView>
</View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
flex: 1,
backgroundColor: theme.centerChannelBg,
},
scrollView: {
flex: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
paddingTop: 10,
},
errorContainer: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
},
errorWrapper: {
justifyContent: 'center',
alignItems: 'center',
},
inputContainer: {
marginTop: 10,
backgroundColor: '#fff',
},
input: {
color: '#333',
fontSize: 14,
height: 40,
paddingHorizontal: 15,
},
titleContainer30: {
flexDirection: 'row',
marginTop: 30,
},
titleContainer15: {
flexDirection: 'row',
marginTop: 15,
},
title: {
fontSize: 14,
color: theme.centerChannelColor,
marginLeft: 15,
},
optional: {
color: changeOpacity(theme.centerChannelColor, 0.5),
fontSize: 14,
marginLeft: 5,
},
helpText: {
fontSize: 14,
color: changeOpacity(theme.centerChannelColor, 0.5),
marginTop: 10,
marginHorizontal: 15,
},
};
});

View file

@ -1,414 +1,23 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {
Platform,
TouchableWithoutFeedback,
View,
Text,
findNodeHandle,
} from 'react-native';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
dismissModal,
popTopScreen,
} from 'app/actions/navigation';
import ErrorText from 'app/components/error_text';
import FormattedText from 'app/components/formatted_text';
import Loading from 'app/components/loading';
import StatusBar from 'app/components/status_bar';
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
import EditChannelInfo from './edit_channel_info';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import {General} from 'mattermost-redux/constants';
import {getShortenedURL} from 'app/utils/url';
import {t} from 'app/utils/i18n';
export default class EditChannelInfo extends PureComponent {
static propTypes = {
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
deviceWidth: PropTypes.number.isRequired,
deviceHeight: PropTypes.number.isRequired,
channelType: PropTypes.string,
enableRightButton: PropTypes.func,
saving: PropTypes.bool.isRequired,
editing: PropTypes.bool,
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
displayName: PropTypes.string,
currentTeamUrl: PropTypes.string,
channelURL: PropTypes.string,
purpose: PropTypes.string,
header: PropTypes.string,
onDisplayNameChange: PropTypes.func,
onChannelURLChange: PropTypes.func,
onPurposeChange: PropTypes.func,
onHeaderChange: PropTypes.func,
oldDisplayName: PropTypes.string,
oldChannelURL: PropTypes.string,
oldHeader: PropTypes.string,
oldPurpose: PropTypes.string,
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
dismissModal,
popTopScreen,
}, dispatch),
};
static defaultProps = {
editing: false,
};
constructor(props) {
super(props);
this.nameInput = React.createRef();
this.urlInput = React.createRef();
this.purposeInput = React.createRef();
this.headerInput = React.createRef();
this.lastText = React.createRef();
this.scroll = React.createRef();
}
blur = () => {
if (this.nameInput?.current) {
this.nameInput.current.blur();
}
// TODO: uncomment below once the channel URL field is added
// if (this.urlInput?.current) {
// this.urlInput.current.blur();
// }
if (this.purposeInput?.current) {
this.purposeInput.current.blur();
}
if (this.headerInput?.current) {
this.headerInput.current.blur();
}
if (this.scroll?.current) {
this.scroll.current.scrollToPosition(0, 0, true);
}
};
close = (goBack = false) => {
if (goBack) {
this.props.navigator.pop({animated: true});
} else {
this.props.navigator.dismissModal({
animationType: 'slide-down',
});
}
};
canUpdate = (displayName, channelURL, purpose, header) => {
const {
oldDisplayName,
oldChannelURL,
oldPurpose,
oldHeader,
} = this.props;
return displayName !== oldDisplayName || channelURL !== oldChannelURL ||
purpose !== oldPurpose || header !== oldHeader;
};
enableRightButton = (enable = false) => {
this.props.enableRightButton(enable);
};
onDisplayNameChangeText = (displayName) => {
const {editing, onDisplayNameChange} = this.props;
onDisplayNameChange(displayName);
if (editing) {
const {channelURL, purpose, header} = this.props;
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
this.enableRightButton(canUpdate);
return;
}
const displayNameExists = displayName && displayName.length >= 2;
this.props.enableRightButton(displayNameExists);
};
onDisplayURLChangeText = (channelURL) => {
const {editing, onChannelURLChange} = this.props;
onChannelURLChange(channelURL);
if (editing) {
const {displayName, purpose, header} = this.props;
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
this.enableRightButton(canUpdate);
}
};
onPurposeChangeText = (purpose) => {
const {editing, onPurposeChange} = this.props;
onPurposeChange(purpose);
if (editing) {
const {displayName, channelURL, header} = this.props;
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
this.enableRightButton(canUpdate);
}
};
onHeaderChangeText = (header) => {
const {editing, onHeaderChange} = this.props;
onHeaderChange(header);
if (editing) {
const {displayName, channelURL, purpose} = this.props;
const canUpdate = this.canUpdate(displayName, channelURL, purpose, header);
this.enableRightButton(canUpdate);
}
};
scrollToEnd = () => {
if (this.scroll?.current && this.lastText?.current) {
this.scroll.current.scrollToFocusedInput(findNodeHandle(this.lastText.current));
}
};
render() {
const {
theme,
editing,
channelType,
currentTeamUrl,
deviceWidth,
deviceHeight,
displayName,
channelURL,
header,
purpose,
} = this.props;
const {error, saving} = this.props;
const fullUrl = currentTeamUrl + '/channels';
const shortUrl = getShortenedURL(fullUrl, 35);
const style = getStyleSheet(theme);
const displayHeaderOnly = channelType === General.DM_CHANNEL ||
channelType === General.GM_CHANNEL;
if (saving) {
return (
<View style={style.container}>
<StatusBar/>
<Loading/>
</View>
);
}
let displayError;
if (error) {
displayError = (
<View style={[style.errorContainer, {width: deviceWidth}]}>
<View style={style.errorWrapper}>
<ErrorText error={error}/>
</View>
</View>
);
}
return (
<View style={style.container}>
<StatusBar/>
<KeyboardAwareScrollView
ref={this.scroll}
style={style.container}
>
{displayError}
<TouchableWithoutFeedback onPress={this.blur}>
<View style={[style.scrollView, {height: deviceHeight + (Platform.OS === 'android' ? 200 : 0)}]}>
{!displayHeaderOnly && (
<View>
<View>
<FormattedText
style={style.title}
id='channel_modal.name'
defaultMessage='Name'
/>
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.nameInput}
value={displayName}
onChangeText={this.onDisplayNameChangeText}
style={style.input}
autoCapitalize='none'
autoCorrect={false}
placeholder={{id: t('channel_modal.nameEx'), defaultMessage: 'E.g.: "Bugs", "Marketing", "客户支持"'}}
placeholderTextColor={changeOpacity('#000', 0.5)}
underlineColorAndroid='transparent'
disableFullscreenUI={true}
/>
</View>
</View>
)}
{/*TODO: Hide channel url field until it's added to CreateChannel */}
{false && editing && !displayHeaderOnly && (
<View>
<View style={style.titleContainer30}>
<FormattedText
style={style.title}
id='rename_channel.url'
defaultMessage='URL'
/>
<Text style={style.optional}>
{shortUrl}
</Text>
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.urlInput}
value={channelURL}
onChangeText={this.onDisplayURLChangeText}
style={style.input}
autoCapitalize='none'
autoCorrect={false}
placeholder={{id: t('rename_channel.handleHolder'), defaultMessage: 'lowercase alphanumeric characters'}}
placeholderTextColor={changeOpacity('#000', 0.5)}
underlineColorAndroid='transparent'
disableFullscreenUI={true}
/>
</View>
</View>
)}
{!displayHeaderOnly && (
<View>
<View style={style.titleContainer30}>
<FormattedText
style={style.title}
id='channel_modal.purpose'
defaultMessage='Purpose'
/>
<FormattedText
style={style.optional}
id='channel_modal.optional'
defaultMessage='(optional)'
/>
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.purposeInput}
value={purpose}
onChangeText={this.onPurposeChangeText}
style={[style.input, {height: 110}]}
autoCapitalize='none'
autoCorrect={false}
placeholder={{id: t('channel_modal.purposeEx'), defaultMessage: 'E.g.: "A channel to file bugs and improvements"'}}
placeholderTextColor={changeOpacity('#000', 0.5)}
multiline={true}
blurOnSubmit={false}
textAlignVertical='top'
underlineColorAndroid='transparent'
disableFullscreenUI={true}
/>
</View>
<View>
<FormattedText
style={style.helpText}
id='channel_modal.descriptionHelp'
defaultMessage='Describe how this channel should be used.'
/>
</View>
</View>
)}
<View style={style.titleContainer15}>
<FormattedText
style={style.title}
id='channel_modal.header'
defaultMessage='Header'
/>
<FormattedText
style={style.optional}
id='channel_modal.optional'
defaultMessage='(optional)'
/>
</View>
<View style={style.inputContainer}>
<TextInputWithLocalizedPlaceholder
ref={this.headerInput}
value={header}
onChangeText={this.onHeaderChangeText}
style={[style.input, {height: 110}]}
autoCapitalize='none'
autoCorrect={false}
placeholder={{id: t('channel_modal.headerEx'), defaultMessage: 'E.g.: "[Link Title](http://example.com)"'}}
placeholderTextColor={changeOpacity('#000', 0.5)}
multiline={true}
blurOnSubmit={false}
onFocus={this.scrollToEnd}
textAlignVertical='top'
underlineColorAndroid='transparent'
disableFullscreenUI={true}
/>
</View>
<View ref={this.lastText}>
<FormattedText
style={style.helpText}
id='channel_modal.headerHelp'
defaultMessage={'Set text that will appear in the header of the channel beside the channel name. For example, include frequently used links by typing [Link Title](http://example.com).'}
/>
</View>
</View>
</TouchableWithoutFeedback>
</KeyboardAwareScrollView>
</View>
);
}
}
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
container: {
flex: 1,
backgroundColor: theme.centerChannelBg,
},
scrollView: {
flex: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
paddingTop: 10,
},
errorContainer: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.03),
},
errorWrapper: {
justifyContent: 'center',
alignItems: 'center',
},
inputContainer: {
marginTop: 10,
backgroundColor: '#fff',
},
input: {
color: '#333',
fontSize: 14,
height: 40,
paddingHorizontal: 15,
},
titleContainer30: {
flexDirection: 'row',
marginTop: 30,
},
titleContainer15: {
flexDirection: 'row',
marginTop: 15,
},
title: {
fontSize: 14,
color: theme.centerChannelColor,
marginLeft: 15,
},
optional: {
color: changeOpacity(theme.centerChannelColor, 0.5),
fontSize: 14,
marginLeft: 5,
},
helpText: {
fontSize: 14,
color: changeOpacity(theme.centerChannelColor, 0.5),
marginTop: 10,
marginHorizontal: 15,
},
};
});
export default connect(null, mapDispatchToProps)(EditChannelInfo);

View file

@ -29,7 +29,6 @@ export default class FileAttachment extends PureComponent {
onLongPress: PropTypes.func,
onPreviewPress: PropTypes.func,
theme: PropTypes.object.isRequired,
navigator: PropTypes.object,
};
static defaultProps = {
@ -93,7 +92,6 @@ export default class FileAttachment extends PureComponent {
deviceWidth,
file,
theme,
navigator,
onLongPress,
} = this.props;
const {data} = file;
@ -120,7 +118,6 @@ export default class FileAttachment extends PureComponent {
ref={this.setDocumentRef}
canDownloadFiles={canDownloadFiles}
file={file}
navigator={navigator}
onLongPress={onLongPress}
theme={theme}
/>

View file

@ -25,7 +25,7 @@ import {DeviceTypes} from 'app/constants/';
import mattermostBucket from 'app/mattermost_bucket';
import {changeOpacity} from 'app/utils/theme';
import FileAttachmentIcon from './file_attachment_icon';
import FileAttachmentIcon from 'app/components/file_attachment_list/file_attachment_icon';
const {DOCUMENTS_PATH} = DeviceTypes;
const DOWNLOADING_OFFSET = 28;
@ -38,13 +38,15 @@ const circularProgressWidth = 4;
export default class FileAttachmentDocument extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
goToScreen: PropTypes.func.isRequired,
}).isRequired,
backgroundColor: PropTypes.string,
canDownloadFiles: PropTypes.bool.isRequired,
iconHeight: PropTypes.number,
iconWidth: PropTypes.number,
file: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
navigator: PropTypes.object,
onLongPress: PropTypes.func,
wrapperHeight: PropTypes.number,
wrapperWidth: PropTypes.number,
@ -192,7 +194,7 @@ export default class FileAttachmentDocument extends PureComponent {
};
previewTextFile = (file, delay = 2000) => {
const {navigator, theme} = this.props;
const {actions} = this.props;
const {data} = file;
const prefix = Platform.OS === 'android' ? 'file:/' : '';
const path = `${DOCUMENTS_PATH}/${data.id}-${file.caption}`;
@ -200,21 +202,13 @@ export default class FileAttachmentDocument extends PureComponent {
setTimeout(async () => {
try {
const content = await readFile;
navigator.push({
screen: 'TextPreview',
title: file.caption,
animated: true,
backButtonTitle: '',
passProps: {
content,
},
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
});
const screen = 'TextPreview';
const title = file.caption;
const passProps = {
content,
};
actions.goToScreen(screen, title, passProps);
this.setState({downloading: false, progress: 0});
} catch (error) {
RNFetchBlob.fs.unlink(path);

View file

@ -0,0 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {goToScreen} from 'app/actions/navigation';
import FileAttachmentDocument from './file_attachment_document';
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
goToScreen,
}, dispatch),
};
}
export default connect(null, mapDispatchToProps)(FileAttachmentDocument);

View file

@ -20,14 +20,16 @@ import FileAttachment from './file_attachment';
export default class FileAttachmentList extends Component {
static propTypes = {
actions: PropTypes.object.isRequired,
actions: PropTypes.shape({
loadFilesForPostIfNecessary: PropTypes.func.isRequired,
showModalOverCurrentContext: PropTypes.func.isRequired,
}).isRequired,
canDownloadFiles: PropTypes.bool.isRequired,
deviceHeight: PropTypes.number.isRequired,
deviceWidth: PropTypes.number.isRequired,
fileIds: PropTypes.array.isRequired,
files: PropTypes.array,
isFailed: PropTypes.bool,
navigator: PropTypes.object,
onLongPress: PropTypes.func,
postId: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
@ -122,11 +124,12 @@ export default class FileAttachmentList extends Component {
};
handlePreviewPress = preventDoubleTap((idx) => {
previewImageAtIndex(this.props.navigator, this.items, idx, this.galleryFiles);
const {actions} = this.props;
previewImageAtIndex(this.items, idx, this.galleryFiles, actions.showModalOverCurrentContext);
});
renderItems = () => {
const {canDownloadFiles, deviceWidth, fileIds, files, navigator} = this.props;
const {canDownloadFiles, deviceWidth, fileIds, files} = this.props;
if (!files.length && fileIds.length > 0) {
return fileIds.map((id, idx) => (
@ -156,7 +159,6 @@ export default class FileAttachmentList extends Component {
file={f}
id={file.id}
index={idx}
navigator={navigator}
onCaptureRef={this.handleCaptureRef}
onPreviewPress={this.handlePreviewPress}
onLongPress={this.props.onLongPress}

View file

@ -16,6 +16,7 @@ describe('PostAttachmentOpenGraph', () => {
const baseProps = {
actions: {
loadFilesForPostIfNecessary,
showModalOverCurrentContext: jest.fn(),
},
canDownloadFiles: true,
deviceHeight: 680,
@ -71,6 +72,7 @@ describe('PostAttachmentOpenGraph', () => {
files: [],
actions: {
loadFilesForPostIfNecessary: loadFilesForPostIfNecessaryMock,
showModalOverCurrentContext: jest.fn(),
},
};

View file

@ -8,6 +8,7 @@ import {canDownloadFilesOnMobile} from 'mattermost-redux/selectors/entities/gene
import {makeGetFilesForPost} from 'mattermost-redux/selectors/entities/files';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {showModalOverCurrentContext} from 'app/actions/navigation';
import {loadFilesForPostIfNecessary} from 'app/actions/views/channel';
import {getDimensions} from 'app/selectors/device';
@ -29,6 +30,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
loadFilesForPostIfNecessary,
showModalOverCurrentContext,
}, dispatch),
};
}

View file

@ -35,7 +35,6 @@ class FormattedMarkdownText extends React.PureComponent {
baseTextStyle: CustomPropTypes.Style,
defaultMessage: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
navigator: PropTypes.object.isRequired,
onPostPress: PropTypes.func,
style: CustomPropTypes.Style,
textStyles: PropTypes.object,
@ -115,7 +114,6 @@ class FormattedMarkdownText extends React.PureComponent {
<AtMention
mentionStyle={this.props.textStyles.mention}
mentionName={mentionName}
navigator={this.props.navigator}
onPostPress={this.props.onPostPress}
textStyle={this.computeTextStyle(this.props.baseTextStyle, context)}
/>

View file

@ -1,8 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {showModal} from 'app/actions/navigation';
import InteractiveDialogController from './interactive_dialog_controller';
function mapStateToProps(state) {
@ -12,4 +15,12 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps)(InteractiveDialogController);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
showModal,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(InteractiveDialogController);

View file

@ -7,9 +7,11 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
export default class InteractiveDialogController extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
showModal: PropTypes.func.isRequired,
}).isRequired,
triggerId: PropTypes.string,
dialog: PropTypes.object,
navigator: PropTypes.object,
theme: PropTypes.object,
};
@ -22,7 +24,7 @@ export default class InteractiveDialogController extends PureComponent {
}
componentDidUpdate(prevProps) {
const triggerId = this.props.triggerId;
const {actions, triggerId} = this.props;
if (!triggerId) {
return;
}
@ -41,33 +43,24 @@ export default class InteractiveDialogController extends PureComponent {
return;
}
const theme = this.props.theme;
this.props.navigator.showModal({
backButtonTitle: '',
screen: 'InteractiveDialog',
title: dialogData.dialog.title,
animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
navigatorButtons: {
const screen = 'InteractiveDialog';
const title = dialogData.dialog.title;
const passProps = {};
const options = {
topBar: {
leftButtons: [{
id: 'close-dialog',
icon: this.closeButton,
}],
rightButtons: [
{
id: 'submit-dialog',
showAsAction: 'always',
title: dialogData.dialog.submit_label,
},
],
rightButtons: [{
id: 'submit-dialog',
showAsAction: 'always',
text: dialogData.dialog.submit_label,
}],
},
});
};
actions.showModal(screen, title, passProps, options);
}
render() {

View file

@ -12,24 +12,31 @@ export default class Hashtag extends React.PureComponent {
hashtag: PropTypes.string.isRequired,
linkStyle: CustomPropTypes.Style.isRequired,
onHashtagPress: PropTypes.func,
navigator: PropTypes.object.isRequired,
actions: PropTypes.shape({
popToRoot: PropTypes.func.isRequired,
showSearchModal: PropTypes.func.isRequired,
dismissAllModals: PropTypes.func.isRequired,
}).isRequired,
};
handlePress = () => {
if (this.props.onHashtagPress) {
this.props.onHashtagPress(this.props.hashtag);
const {
onHashtagPress,
hashtag,
actions,
} = this.props;
if (onHashtagPress) {
onHashtagPress(hashtag);
return;
}
// Close thread view, permalink view, etc
this.props.navigator.dismissAllModals();
this.props.navigator.popToRoot();
actions.dismissAllModals();
actions.popToRoot();
this.props.actions.showSearchModal(this.props.navigator, '#' + this.props.hashtag);
actions.showSearchModal('#' + this.props.hashtag);
};
render() {

View file

@ -11,12 +11,10 @@ describe('Hashtag', () => {
const baseProps = {
hashtag: 'test',
linkStyle: {color: 'red'},
navigator: {
dismissAllModals: jest.fn(),
popToRoot: jest.fn(),
},
actions: {
showSearchModal: jest.fn(),
dismissAllModals: jest.fn(),
popToRoot: jest.fn(),
},
};
@ -35,9 +33,9 @@ describe('Hashtag', () => {
wrapper.find(Text).simulate('press');
expect(props.navigator.dismissAllModals).toHaveBeenCalled();
expect(props.navigator.popToRoot).toHaveBeenCalled();
expect(props.actions.showSearchModal).toHaveBeenCalledWith(props.navigator, '#test');
expect(props.actions.dismissAllModals).toHaveBeenCalled();
expect(props.actions.popToRoot).toHaveBeenCalled();
expect(props.actions.showSearchModal).toHaveBeenCalledWith('#test');
});
test('should call onHashtagPress if provided', () => {
@ -50,8 +48,8 @@ describe('Hashtag', () => {
wrapper.find(Text).simulate('press');
expect(props.navigator.dismissAllModals).not.toBeCalled();
expect(props.navigator.popToRoot).not.toBeCalled();
expect(props.actions.dismissAllModals).not.toBeCalled();
expect(props.actions.popToRoot).not.toBeCalled();
expect(props.actions.showSearchModal).not.toBeCalled();
expect(props.onHashtagPress).toBeCalled();

View file

@ -4,14 +4,20 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {showSearchModal} from 'app/actions/views/search';
import {
popToRoot,
showSearchModal,
dismissAllModals,
} from 'app/actions/navigation';
import Hashtag from './hashtag';
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
popToRoot,
showSearchModal,
dismissAllModals,
}, dispatch),
};
}

View file

@ -49,7 +49,6 @@ export default class Markdown extends PureComponent {
isSearchResult: PropTypes.bool,
mentionKeys: PropTypes.array.isRequired,
minimumHashtagLength: PropTypes.number.isRequired,
navigator: PropTypes.object.isRequired,
onChannelLinkPress: PropTypes.func,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
@ -176,7 +175,6 @@ export default class Markdown extends PureComponent {
<MarkdownTableImage
source={src}
textStyle={[this.computeTextStyle(this.props.baseTextStyle, context), this.props.textStyles.link]}
navigator={this.props.navigator}
>
{reactChildren}
</MarkdownTableImage>
@ -188,7 +186,6 @@ export default class Markdown extends PureComponent {
linkDestination={linkDestination}
imagesMetadata={this.props.imagesMetadata}
isReplyPost={this.props.isReplyPost}
navigator={this.props.navigator}
source={src}
errorTextStyle={[this.computeTextStyle(this.props.baseTextStyle, context), this.props.textStyles.error]}
>
@ -209,7 +206,6 @@ export default class Markdown extends PureComponent {
isSearchResult={this.props.isSearchResult}
mentionName={mentionName}
onPostPress={this.props.onPostPress}
navigator={this.props.navigator}
/>
);
};
@ -250,7 +246,6 @@ export default class Markdown extends PureComponent {
hashtag={hashtag}
linkStyle={this.props.textStyles.link}
onHashtagPress={this.props.onHashtagPress}
navigator={this.props.navigator}
/>
);
};
@ -295,7 +290,6 @@ export default class Markdown extends PureComponent {
return (
<MarkdownCodeBlock
navigator={this.props.navigator}
content={content}
language={props.language}
textStyle={this.props.textStyles.codeBlock}
@ -371,7 +365,6 @@ export default class Markdown extends PureComponent {
renderTable = ({children, numColumns}) => {
return (
<MarkdownTable
navigator={this.props.navigator}
numColumns={numColumns}
>
{children}

View file

@ -1,10 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {goToScreen} from 'app/actions/navigation';
import MarkdownCodeBlock from './markdown_code_block';
function mapStateToProps(state) {
@ -13,4 +16,12 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps)(MarkdownCodeBlock);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
goToScreen,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(MarkdownCodeBlock);

View file

@ -24,7 +24,9 @@ const MAX_LINES = 4;
export default class MarkdownCodeBlock extends React.PureComponent {
static propTypes = {
navigator: PropTypes.object.isRequired,
actions: PropTypes.shape({
goToScreen: PropTypes.func.isRequired,
}).isRequired,
theme: PropTypes.object.isRequired,
language: PropTypes.string,
content: PropTypes.string.isRequired,
@ -40,10 +42,14 @@ export default class MarkdownCodeBlock extends React.PureComponent {
};
handlePress = preventDoubleTap(() => {
const {navigator, theme} = this.props;
const {actions, language, content} = this.props;
const {intl} = this.context;
const screen = 'Code';
const passProps = {
content,
};
const languageDisplayName = getDisplayNameForLanguage(this.props.language);
const languageDisplayName = getDisplayNameForLanguage(language);
let title;
if (languageDisplayName) {
title = intl.formatMessage(
@ -62,21 +68,7 @@ export default class MarkdownCodeBlock extends React.PureComponent {
});
}
navigator.push({
screen: 'Code',
title,
animated: true,
backButtonTitle: '',
passProps: {
content: this.props.content,
},
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
});
actions.goToScreen(screen, title, passProps);
});
handleLongPress = async () => {

View file

@ -1,10 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
import {showModalOverCurrentContext} from 'app/actions/navigation';
import {getDimensions} from 'app/selectors/device';
import MarkdownImage from './markdown_image';
@ -16,4 +19,12 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps)(MarkdownImage);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
showModalOverCurrentContext,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(MarkdownImage);

View file

@ -33,13 +33,15 @@ const VIEWPORT_IMAGE_REPLY_OFFSET = 13;
export default class MarkdownImage extends React.Component {
static propTypes = {
actions: PropTypes.shape({
showModalOverCurrentContext: PropTypes.func.isRequired,
}).isRequired,
children: PropTypes.node,
deviceHeight: PropTypes.number.isRequired,
deviceWidth: PropTypes.number.isRequired,
imagesMetadata: PropTypes.object,
linkDestination: PropTypes.string,
isReplyPost: PropTypes.bool,
navigator: PropTypes.object.isRequired,
serverURL: PropTypes.string.isRequired,
source: PropTypes.string.isRequired,
errorTextStyle: CustomPropTypes.Style,
@ -175,6 +177,7 @@ export default class MarkdownImage extends React.Component {
originalWidth,
uri,
} = this.state;
const {actions} = this.props;
const link = this.getSource();
let filename = link.substring(link.lastIndexOf('/') + 1, link.indexOf('?') === -1 ? link.length : link.indexOf('?'));
const extension = filename.split('.').pop();
@ -195,7 +198,8 @@ export default class MarkdownImage extends React.Component {
localPath: uri,
},
}];
previewImageAtIndex(this.props.navigator, [this.refs.item], 0, files);
previewImageAtIndex([this.refs.item], 0, files, actions.showModalOverCurrentContext);
};
loadImageSize = (source) => {

View file

@ -1,10 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {goToScreen} from 'app/actions/navigation';
import MarkdownTable from './markdown_table';
function mapStateToProps(state) {
@ -13,4 +16,12 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps)(MarkdownTable);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
goToScreen,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(MarkdownTable);

View file

@ -19,8 +19,10 @@ const MAX_HEIGHT = 300;
export default class MarkdownTable extends React.PureComponent {
static propTypes = {
actions: PropTypes.shape({
goToScreen: PropTypes.func.isRequired,
}).isRequired,
children: PropTypes.node.isRequired,
navigator: PropTypes.object.isRequired,
numColumns: PropTypes.number.isRequired,
theme: PropTypes.object.isRequired,
};
@ -44,27 +46,19 @@ export default class MarkdownTable extends React.PureComponent {
};
handlePress = preventDoubleTap(() => {
const {navigator, theme} = this.props;
navigator.push({
screen: 'Table',
title: this.context.intl.formatMessage({
id: 'mobile.routes.table',
defaultMessage: 'Table',
}),
animated: true,
backButtonTitle: '',
passProps: {
renderRows: this.renderRows,
tableWidth: this.getTableWidth(),
},
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
const {actions} = this.props;
const {intl} = this.context;
const screen = 'Table';
const title = intl.formatMessage({
id: 'mobile.routes.table',
defaultMessage: 'Table',
});
const passProps = {
renderRows: this.renderRows,
tableWidth: this.getTableWidth(),
};
actions.goToScreen(screen, title, passProps);
});
handleContainerLayout = (e) => {

View file

@ -1,11 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {goToScreen} from 'app/actions/navigation';
import MarkdownTableImage from './markdown_table_image';
function mapStateToProps(state) {
@ -15,4 +18,12 @@ function mapStateToProps(state) {
};
}
export default connect(mapStateToProps)(MarkdownTableImage);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
goToScreen,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(MarkdownTableImage);

View file

@ -11,10 +11,12 @@ import {preventDoubleTap} from 'app/utils/tap';
export default class MarkdownTableImage extends React.PureComponent {
static propTypes = {
actions: PropTypes.shape({
goToScreen: PropTypes.func.isRequired,
}).isRequired,
children: PropTypes.node.isRequired,
source: PropTypes.string.isRequired,
textStyle: CustomPropTypes.Style.isRequired,
navigator: PropTypes.object.isRequired,
serverURL: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
};
@ -24,26 +26,18 @@ export default class MarkdownTableImage extends React.PureComponent {
};
handlePress = preventDoubleTap(() => {
const {navigator, theme} = this.props;
navigator.push({
screen: 'TableImage',
title: this.context.intl.formatMessage({
id: 'mobile.routes.tableImage',
defaultMessage: 'Image',
}),
animated: true,
backButtonTitle: '',
passProps: {
imageSource: this.getImageSource(),
},
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
const {actions} = this.props;
const {intl} = this.context;
const screen = 'TableImage';
const title = intl.formatMessage({
id: 'mobile.routes.tableImage',
defaultMessage: 'Image',
});
const passProps = {
imageSource: this.getImageSource(),
};
actions.goToScreen(screen, title, passProps);
});
getImageSource = () => {

View file

@ -18,7 +18,6 @@ export default class ActionMenu extends PureComponent {
options: PropTypes.arrayOf(PropTypes.object),
postId: PropTypes.string.isRequired,
selected: PropTypes.object,
navigator: PropTypes.object,
};
constructor(props) {
@ -63,7 +62,6 @@ export default class ActionMenu extends PureComponent {
name,
dataSource,
options,
navigator,
} = this.props;
const {selected} = this.state;
@ -73,7 +71,6 @@ export default class ActionMenu extends PureComponent {
dataSource={dataSource}
options={options}
selected={selected}
navigator={navigator}
onSelected={this.handleSelect}
/>
);

View file

@ -10,14 +10,12 @@ import ActionButton from './action_button';
export default class AttachmentActions extends PureComponent {
static propTypes = {
actions: PropTypes.array,
navigator: PropTypes.object.isRequired,
postId: PropTypes.string.isRequired,
};
render() {
const {
actions,
navigator,
postId,
} = this.props;
@ -43,7 +41,6 @@ export default class AttachmentActions extends PureComponent {
defaultOption={action.default_option}
options={action.options}
postId={postId}
navigator={navigator}
/>
);
break;

View file

@ -15,7 +15,6 @@ export default class AttachmentFields extends PureComponent {
blockStyles: PropTypes.object.isRequired,
fields: PropTypes.array,
metadata: PropTypes.object,
navigator: PropTypes.object.isRequired,
onPermalinkPress: PropTypes.func,
textStyles: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
@ -27,7 +26,6 @@ export default class AttachmentFields extends PureComponent {
blockStyles,
fields,
metadata,
navigator,
onPermalinkPress,
textStyles,
theme,
@ -88,7 +86,6 @@ export default class AttachmentFields extends PureComponent {
blockStyles={blockStyles}
imagesMetadata={metadata?.images}
value={(field.value || '')}
navigator={navigator}
onPermalinkPress={onPermalinkPress}
/>
</View>

View file

@ -15,11 +15,13 @@ const VIEWPORT_IMAGE_CONTAINER_OFFSET = 10;
export default class AttachmentImage extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
showModalOverCurrentContext: PropTypes.func.isRequired,
}).isRequired,
deviceHeight: PropTypes.number.isRequired,
deviceWidth: PropTypes.number.isRequired,
imageMetadata: PropTypes.object,
imageUrl: PropTypes.string,
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
};
@ -47,7 +49,7 @@ export default class AttachmentImage extends PureComponent {
}
handlePreviewImage = () => {
const {imageUrl, navigator} = this.props;
const {actions, imageUrl} = this.props;
const {
imageUri: uri,
originalHeight,
@ -73,7 +75,7 @@ export default class AttachmentImage extends PureComponent {
localPath: uri,
},
}];
previewImageAtIndex(navigator, [this.refs.item], 0, files);
previewImageAtIndex([this.refs.item], 0, files, actions.showModalOverCurrentContext);
};
setImageDimensions = (imageUri, dimensions, originalWidth, originalHeight) => {

View file

@ -0,0 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {showModalOverCurrentContext} from 'app/actions/navigation';
import AttachmentImage from './attachment_image';
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
showModalOverCurrentContext,
}, dispatch),
};
}
export default connect(null, mapDispatchToProps)(AttachmentImage);

View file

@ -13,7 +13,6 @@ export default class AttachmentPreText extends PureComponent {
baseTextStyle: CustomPropTypes.Style.isRequired,
blockStyles: PropTypes.object.isRequired,
metadata: PropTypes.object,
navigator: PropTypes.object.isRequired,
onPermalinkPress: PropTypes.func,
textStyles: PropTypes.object.isRequired,
value: PropTypes.string,
@ -24,7 +23,6 @@ export default class AttachmentPreText extends PureComponent {
baseTextStyle,
blockStyles,
metadata,
navigator,
onPermalinkPress,
value,
textStyles,
@ -42,7 +40,6 @@ export default class AttachmentPreText extends PureComponent {
blockStyles={blockStyles}
imagesMetadata={metadata?.images}
value={value}
navigator={navigator}
onPermalinkPress={onPermalinkPress}
/>
</View>

View file

@ -18,7 +18,6 @@ export default class AttachmentText extends PureComponent {
deviceHeight: PropTypes.number.isRequired,
hasThumbnail: PropTypes.bool,
metadata: PropTypes.object,
navigator: PropTypes.object.isRequired,
onPermalinkPress: PropTypes.func,
textStyles: PropTypes.object.isRequired,
value: PropTypes.string,
@ -68,7 +67,6 @@ export default class AttachmentText extends PureComponent {
blockStyles,
hasThumbnail,
metadata,
navigator,
onPermalinkPress,
value,
textStyles,
@ -97,7 +95,6 @@ export default class AttachmentText extends PureComponent {
blockStyles={blockStyles}
imagesMetadata={metadata?.images}
value={value}
navigator={navigator}
onPermalinkPress={onPermalinkPress}
/>
</View>

View file

@ -13,7 +13,6 @@ export default class AttachmentTitle extends PureComponent {
link: PropTypes.string,
theme: PropTypes.object.isRequired,
value: PropTypes.string,
navigator: PropTypes.object.isRequired,
};
openLink = () => {
@ -28,7 +27,6 @@ export default class AttachmentTitle extends PureComponent {
link,
value,
theme,
navigator,
} = this.props;
if (!value) {
@ -57,7 +55,6 @@ export default class AttachmentTitle extends PureComponent {
disableChannelLink={true}
autolinkedUrlSchemes={[]}
mentionKeys={[]}
navigator={navigator}
theme={theme}
value={value}
baseTextStyle={style.title}

View file

@ -18,7 +18,6 @@ export default class MessageAttachments extends PureComponent {
deviceWidth: PropTypes.number.isRequired,
postId: PropTypes.string.isRequired,
metadata: PropTypes.object,
navigator: PropTypes.object.isRequired,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
theme: PropTypes.object,
@ -33,7 +32,6 @@ export default class MessageAttachments extends PureComponent {
deviceHeight,
deviceWidth,
metadata,
navigator,
onHashtagPress,
onPermalinkPress,
postId,
@ -52,7 +50,6 @@ export default class MessageAttachments extends PureComponent {
deviceWidth={deviceWidth}
key={'att_' + i}
metadata={metadata}
navigator={navigator}
onHashtagPress={onHashtagPress}
onPermalinkPress={onPermalinkPress}
postId={postId}

View file

@ -31,7 +31,6 @@ export default class MessageAttachment extends PureComponent {
deviceHeight: PropTypes.number.isRequired,
deviceWidth: PropTypes.number.isRequired,
metadata: PropTypes.object,
navigator: PropTypes.object.isRequired,
postId: PropTypes.string.isRequired,
onPermalinkPress: PropTypes.func,
theme: PropTypes.object,
@ -46,7 +45,6 @@ export default class MessageAttachment extends PureComponent {
deviceHeight,
deviceWidth,
metadata,
navigator,
onPermalinkPress,
postId,
textStyles,
@ -70,7 +68,6 @@ export default class MessageAttachment extends PureComponent {
baseTextStyle={baseTextStyle}
blockStyles={blockStyles}
metadata={metadata}
navigator={navigator}
onPermalinkPress={onPermalinkPress}
textStyles={textStyles}
value={attachment.pretext}
@ -86,7 +83,6 @@ export default class MessageAttachment extends PureComponent {
link={attachment.title_link}
theme={theme}
value={attachment.title}
navigator={navigator}
/>
<AttachmentThumbnail url={attachment.thumb_url}/>
<AttachmentText
@ -95,7 +91,6 @@ export default class MessageAttachment extends PureComponent {
deviceHeight={deviceHeight}
hasThumbnail={Boolean(attachment.thumb_url)}
metadata={metadata}
navigator={navigator}
onPermalinkPress={onPermalinkPress}
textStyles={textStyles}
value={attachment.text}
@ -105,14 +100,12 @@ export default class MessageAttachment extends PureComponent {
blockStyles={blockStyles}
fields={attachment.fields}
metadata={metadata}
navigator={navigator}
onPermalinkPress={onPermalinkPress}
textStyles={textStyles}
theme={theme}
/>
<AttachmentActions
actions={attachment.actions}
navigator={navigator}
postId={postId}
/>
<AttachmentImage
@ -120,7 +113,6 @@ export default class MessageAttachment extends PureComponent {
deviceWidth={deviceWidth}
imageUrl={attachment.image_url}
imageMetadata={metadata?.images?.[attachment.image_url]}
navigator={navigator}
theme={theme}
/>
</View>

View file

@ -12,6 +12,7 @@ import {getUser, getCurrentUserId} from 'mattermost-redux/selectors/entities/use
import {getMyPreferences, getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {isPostFlagged, isSystemMessage} from 'mattermost-redux/utils/post_utils';
import {goToScreen, showModalOverCurrentContext} from 'app/actions/navigation';
import {insertToDraft, setPostTooltipVisible} from 'app/actions/views/channel';
import Post from './post';
@ -93,6 +94,8 @@ function mapDispatchToProps(dispatch) {
removePost,
setPostTooltipVisible,
insertToDraft,
goToScreen,
showModalOverCurrentContext,
}, dispatch),
};
}

View file

@ -34,6 +34,8 @@ export default class Post extends PureComponent {
createPost: PropTypes.func.isRequired,
insertToDraft: PropTypes.func.isRequired,
removePost: PropTypes.func.isRequired,
goToScreen: PropTypes.func.isRequired,
showModalOverCurrentContext: PropTypes.func.isRequired,
}).isRequired,
channelIsReadOnly: PropTypes.bool,
currentUserId: PropTypes.string.isRequired,
@ -49,7 +51,6 @@ export default class Post extends PureComponent {
isSearchResult: PropTypes.bool,
commentedOnPost: PropTypes.object,
managedConfig: PropTypes.object.isRequired,
navigator: PropTypes.object,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
shouldRenderReplyButton: PropTypes.bool,
@ -88,30 +89,16 @@ export default class Post extends PureComponent {
goToUserProfile = () => {
const {intl} = this.context;
const {navigator, post, theme} = this.props;
const options = {
screen: 'UserProfile',
title: intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}),
animated: true,
backButtonTitle: '',
passProps: {
userId: post.user_id,
},
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
const {actions, post} = this.props;
const screen = 'UserProfile';
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
const passProps = {
userId: post.user_id,
};
Keyboard.dismiss();
requestAnimationFrame(() => {
if (Platform.OS === 'ios') {
navigator.push(options);
} else {
navigator.showModal(options);
}
actions.goToScreen(screen, title, passProps);
});
};
@ -120,7 +107,8 @@ export default class Post extends PureComponent {
};
handleFailedPostPress = () => {
const options = {
const screen = 'OptionsModal';
const passProps = {
title: {
id: t('mobile.post.failed_title'),
defaultMessage: 'Unable to send your message:',
@ -151,22 +139,7 @@ export default class Post extends PureComponent {
}],
};
this.props.navigator.showModal({
screen: 'OptionsModal',
title: '',
animationType: 'none',
passProps: {
items: options.items,
title: options.title,
},
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
});
this.props.actions.showModalOverCurrentContext(screen, passProps);
};
handlePress = preventDoubleTap(() => {
@ -352,7 +325,6 @@ export default class Post extends PureComponent {
channelIsReadOnly={channelIsReadOnly}
isLastPost={isLastPost}
isSearchResult={isSearchResult}
navigator={this.props.navigator}
onFailedPostPress={this.handleFailedPostPress}
onHashtagPress={onHashtagPress}
onPermalinkPress={onPermalinkPress}

View file

@ -31,7 +31,6 @@ export default class PostAddChannelMember extends React.PureComponent {
userIds: PropTypes.array.isRequired,
usernames: PropTypes.array.isRequired,
noGroupsUsernames: PropTypes.array,
navigator: PropTypes.object.isRequired,
onPostPress: PropTypes.func,
textStyles: PropTypes.object,
};
@ -90,7 +89,6 @@ export default class PostAddChannelMember extends React.PureComponent {
mentionStyle={this.props.textStyles.mention}
mentionName={usernames[0]}
onPostPress={this.props.onPostPress}
navigator={this.props.navigator}
/>
);
} else if (usernames.length > 1) {
@ -119,7 +117,6 @@ export default class PostAddChannelMember extends React.PureComponent {
mentionStyle={this.props.textStyles.mention}
mentionName={username}
onPostPress={this.props.onPostPress}
navigator={this.props.navigator}
/>
);
}).reduce((acc, el, idx, arr) => {

View file

@ -6,6 +6,8 @@ import {bindActionCreators} from 'redux';
import {getOpenGraphMetadata} from 'mattermost-redux/actions/posts';
import {showModalOverCurrentContext} from 'app/actions/navigation';
import {getDimensions} from 'app/selectors/device';
import PostAttachmentOpenGraph from './post_attachment_opengraph';
@ -20,6 +22,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getOpenGraphMetadata,
showModalOverCurrentContext,
}, dispatch),
};
}

View file

@ -28,13 +28,13 @@ export default class PostAttachmentOpenGraph extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
getOpenGraphMetadata: PropTypes.func.isRequired,
showModalOverCurrentContext: PropTypes.func.isRequired,
}).isRequired,
deviceHeight: PropTypes.number.isRequired,
deviceWidth: PropTypes.number.isRequired,
imagesMetadata: PropTypes.object,
isReplyPost: PropTypes.bool,
link: PropTypes.string.isRequired,
navigator: PropTypes.object.isRequired,
openGraphData: PropTypes.object,
theme: PropTypes.object.isRequired,
};
@ -195,6 +195,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
originalWidth,
originalHeight,
} = this.state;
const {actions} = this.props;
const filename = this.getFilename(link);
const files = [{
@ -209,7 +210,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
},
}];
previewImageAtIndex(this.props.navigator, [this.refs.item], 0, files);
previewImageAtIndex([this.refs.item], 0, files, actions.showModalOverCurrentContext);
};
renderDescription = () => {

View file

@ -25,6 +25,7 @@ describe('PostAttachmentOpenGraph', () => {
const baseProps = {
actions: {
getOpenGraphMetadata: jest.fn(),
showModalOverCurrentContext: jest.fn(),
},
deviceHeight: 600,
deviceWidth: 400,
@ -36,7 +37,6 @@ describe('PostAttachmentOpenGraph', () => {
},
isReplyPost: false,
link: 'https://mattermost.com/',
navigator: {},
theme: Preferences.THEMES.default,
};

View file

@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {General, Posts} from 'mattermost-redux/constants';
@ -21,6 +22,8 @@ import {
} from 'mattermost-redux/utils/post_utils';
import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
import {showModalOverCurrentContext} from 'app/actions/navigation';
import {getDimensions} from 'app/selectors/device';
import {hasEmojisOnly} from 'app/utils/emoji_utils';
@ -102,4 +105,12 @@ function makeMapStateToProps() {
};
}
export default connect(makeMapStateToProps, null, null, {forwardRef: true})(PostBody);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
showModalOverCurrentContext,
}, dispatch),
};
}
export default connect(makeMapStateToProps, mapDispatchToProps, null, {forwardRef: true})(PostBody);

View file

@ -36,6 +36,9 @@ const SHOW_MORE_HEIGHT = 60;
export default class PostBody extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
showModalOverCurrentContext: PropTypes.func.isRequired,
}).isRequired,
canDelete: PropTypes.bool,
channelIsReadOnly: PropTypes.bool.isRequired,
deviceHeight: PropTypes.number.isRequired,
@ -56,7 +59,6 @@ export default class PostBody extends PureComponent {
metadata: PropTypes.object,
managedConfig: PropTypes.object,
message: PropTypes.string,
navigator: PropTypes.object.isRequired,
onFailedPostPress: PropTypes.func,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
@ -130,31 +132,25 @@ export default class PostBody extends PureComponent {
openLongPost = preventDoubleTap(() => {
const {
managedConfig,
navigator,
onHashtagPress,
onPermalinkPress,
post,
actions,
} = this.props;
const screen = 'LongPost';
const passProps = {
postId: post.id,
managedConfig,
onHashtagPress,
onPermalinkPress,
};
const options = {
screen: 'LongPost',
animationType: 'none',
backButtonTitle: '',
overrideBackPress: true,
navigatorStyle: {
navBarHidden: true,
screenBackgroundColor: changeOpacity('#000', 0.2),
modalPresentationStyle: 'overCurrentContext',
},
passProps: {
postId: post.id,
managedConfig,
onHashtagPress,
onPermalinkPress,
layout: {
backgroundColor: changeOpacity('#000', 0.2),
},
};
navigator.showModal(options);
actions.showModalOverCurrentContext(screen, passProps, options);
});
showPostOptions = () => {
@ -168,10 +164,10 @@ export default class PostBody extends PureComponent {
isPostEphemeral,
isSystemMessage,
managedConfig,
navigator,
post,
showAddReaction,
location,
actions,
} = this.props;
if (isSystemMessage && (!canDelete || hasBeenDeleted)) {
@ -182,32 +178,22 @@ export default class PostBody extends PureComponent {
return;
}
const options = {
screen: 'PostOptions',
animationType: 'none',
backButtonTitle: '',
navigatorStyle: {
navBarHidden: true,
navBarTransparent: true,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
passProps: {
canDelete,
channelIsReadOnly,
hasBeenDeleted,
isFlagged,
isSystemMessage,
post,
managedConfig,
showAddReaction,
location,
},
const screen = 'PostOptions';
const passProps = {
canDelete,
channelIsReadOnly,
hasBeenDeleted,
isFlagged,
isSystemMessage,
post,
managedConfig,
showAddReaction,
location,
};
Keyboard.dismiss();
requestAnimationFrame(() => {
navigator.showModal(options);
actions.showModalOverCurrentContext(screen, passProps);
});
};
@ -231,7 +217,6 @@ export default class PostBody extends PureComponent {
return (
<PostAddChannelMember
baseTextStyle={messageStyle}
navigator={navigator}
onPostPress={onPress}
textStyles={textStyles}
postId={postProps.add_channel_member.post_id}
@ -246,7 +231,6 @@ export default class PostBody extends PureComponent {
const {
fileIds,
isFailed,
navigator,
post,
showLongPost,
} = this.props;
@ -267,7 +251,6 @@ export default class PostBody extends PureComponent {
isFailed={isFailed}
onLongPress={this.showPostOptions}
postId={post.id}
navigator={navigator}
/>
);
}
@ -281,7 +264,6 @@ export default class PostBody extends PureComponent {
isSystemMessage,
message,
metadata,
navigator,
onHashtagPress,
onPermalinkPress,
post,
@ -304,7 +286,6 @@ export default class PostBody extends PureComponent {
<PostBodyAdditionalContent
baseTextStyle={messageStyle}
blockStyles={blockStyles}
navigator={navigator}
message={message}
metadata={metadata}
postId={post.id}
@ -321,7 +302,6 @@ export default class PostBody extends PureComponent {
const {
hasReactions,
isSearchResult,
navigator,
post,
showLongPost,
} = this.props;
@ -337,7 +317,6 @@ export default class PostBody extends PureComponent {
return (
<Reactions
postId={post.id}
navigator={navigator}
/>
);
};
@ -356,7 +335,6 @@ export default class PostBody extends PureComponent {
isSystemMessage,
message,
metadata,
navigator,
onFailedPostPress,
onHashtagPress,
onPermalinkPress,
@ -395,7 +373,6 @@ export default class PostBody extends PureComponent {
allUsernames={allUsernames}
linkStyle={textStyles.link}
messageData={messageData}
navigator={navigator}
textStyles={textStyles}
theme={theme}
/>
@ -424,7 +401,6 @@ export default class PostBody extends PureComponent {
isEdited={hasBeenEdited}
isReplyPost={isReplyPost}
isSearchResult={isSearchResult}
navigator={navigator}
onHashtagPress={onHashtagPress}
onPermalinkPress={onPermalinkPress}
onPostPress={onPress}

View file

@ -12,6 +12,9 @@ import PostBody from './post_body.js';
describe('PostBody', () => {
const baseProps = {
actions: {
showModalOverCurrentContext: jest.fn(),
},
canDelete: true,
channelIsReadOnly: false,
deviceHeight: 1920,
@ -30,7 +33,6 @@ describe('PostBody', () => {
isSystemMessage: false,
managedConfig: {},
message: 'Hello, World!',
navigator: {},
onFailedPostPress: jest.fn(),
onHashtagPress: jest.fn(),
onPermalinkPress: jest.fn(),

View file

@ -10,6 +10,7 @@ import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getOpenGraphMetadataForUrl} from 'mattermost-redux/selectors/entities/posts';
import {getBool, getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {showModalOverCurrentContext} from 'app/actions/navigation';
import {ViewTypes} from 'app/constants';
import {getDimensions} from 'app/selectors/device';
import {extractFirstLink} from 'app/utils/url';
@ -75,6 +76,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getRedirectLocation,
showModalOverCurrentContext,
}, dispatch),
};
}

View file

@ -38,6 +38,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
getRedirectLocation: PropTypes.func.isRequired,
showModalOverCurrentContext: PropTypes.func.isRequired,
}).isRequired,
baseTextStyle: CustomPropTypes.Style,
blockStyles: PropTypes.object,
@ -48,7 +49,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
isReplyPost: PropTypes.bool,
link: PropTypes.string,
message: PropTypes.string.isRequired,
navigator: PropTypes.object.isRequired,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
openGraphData: PropTypes.object,
@ -182,7 +182,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
};
generateStaticEmbed = (isYouTube, isImage) => {
const {isReplyPost, link, metadata, navigator, openGraphData, showLinkPreviews, theme} = this.props;
const {isReplyPost, link, metadata, openGraphData, showLinkPreviews, theme} = this.props;
if (isYouTube || (isImage && !openGraphData)) {
return null;
@ -206,7 +206,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
<PostAttachmentOpenGraph
isReplyPost={isReplyPost}
link={link}
navigator={navigator}
openGraphData={openGraphData}
imagesMetadata={metadata && metadata.images}
theme={theme}
@ -343,7 +342,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
deviceHeight,
deviceWidth,
metadata,
navigator,
onHashtagPress,
onPermalinkPress,
textStyles,
@ -364,7 +362,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
deviceHeight={deviceHeight}
deviceWidth={deviceWidth}
metadata={metadata}
navigator={navigator}
postId={postId}
textStyles={textStyles}
theme={theme}
@ -413,7 +410,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
handlePreviewImage = (imageRef) => {
const {shortenedLink} = this.state;
let {link} = this.props;
const {navigator} = this.props;
const {actions} = this.props;
if (shortenedLink) {
link = shortenedLink;
}
@ -435,7 +432,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
},
}];
previewImageAtIndex(navigator, [imageRef], 0, files);
previewImageAtIndex([imageRef], 0, files, actions.showModalOverCurrentContext);
};
playYouTubeVideo = () => {

View file

@ -9,6 +9,7 @@ import {getConfig, getCurrentUrl} from 'mattermost-redux/selectors/entities/gene
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {makePreparePostIdsForPostList, START_OF_NEW_MESSAGES} from 'mattermost-redux/utils/post_list';
import {showModalOverCurrentContext} from 'app/actions/navigation';
import {handleSelectChannelByName, loadChannelsByTeamName, refreshChannelWithRetry} from 'app/actions/views/channel';
import {setDeepLinkURL} from 'app/actions/views/root';
@ -42,6 +43,7 @@ function mapDispatchToProps(dispatch) {
refreshChannelWithRetry,
selectFocusedPostId,
setDeepLinkURL,
showModalOverCurrentContext,
}, dispatch),
};
}

View file

@ -41,6 +41,7 @@ export default class PostList extends PureComponent {
refreshChannelWithRetry: PropTypes.func.isRequired,
selectFocusedPostId: PropTypes.func.isRequired,
setDeepLinkURL: PropTypes.func.isRequired,
showModalOverCurrentContext: PropTypes.func.isRequired,
}).isRequired,
channelId: PropTypes.string,
deepLinkURL: PropTypes.string,
@ -51,7 +52,6 @@ export default class PostList extends PureComponent {
isSearchResult: PropTypes.bool,
lastPostIndex: PropTypes.number.isRequired,
lastViewedAt: PropTypes.number, // Used by container // eslint-disable-line no-unused-prop-types
navigator: PropTypes.object,
onLoadMoreUp: PropTypes.func,
onHashtagPress: PropTypes.func,
onPermalinkPress: PropTypes.func,
@ -250,7 +250,6 @@ export default class PostList extends PureComponent {
isSearchResult: this.props.isSearchResult,
location: this.props.location,
managedConfig: mattermostManaged.getCachedConfig(),
navigator: this.props.navigator,
onHashtagPress: this.props.onHashtagPress,
onPermalinkPress: this.handlePermalinkPress,
onPress: this.props.onPostPress,
@ -307,29 +306,24 @@ export default class PostList extends PureComponent {
};
showPermalinkView = (postId) => {
const {actions, navigator} = this.props;
const {actions} = this.props;
actions.selectFocusedPostId(postId);
if (!this.showingPermalink) {
const screen = 'Permalink';
const passProps = {
isPermalink: true,
onClose: this.handleClosePermalink,
};
const options = {
screen: 'Permalink',
animationType: 'none',
backButtonTitle: '',
overrideBackPress: true,
navigatorStyle: {
navBarHidden: true,
screenBackgroundColor: changeOpacity('#000', 0.2),
modalPresentationStyle: 'overCurrentContext',
},
passProps: {
isPermalink: true,
onClose: this.handleClosePermalink,
layout: {
backgroundColor: changeOpacity('#000', 0.2),
},
};
this.showingPermalink = true;
navigator.showModal(options);
actions.showModalOverCurrentContext(screen, passProps, options);
}
};

View file

@ -18,11 +18,9 @@ describe('PostList', () => {
refreshChannelWithRetry: jest.fn(),
selectFocusedPostId: jest.fn(),
setDeepLinkURL: jest.fn(),
showModalOverCurrentContext: jest.fn(),
},
deepLinkURL: '',
navigator: {
showModal: jest.fn(),
},
lastPostIndex: -1,
postIds: ['post-id-1', 'post-id-2'],
serverURL,
@ -47,7 +45,7 @@ describe('PostList', () => {
wrapper.setProps({deepLinkURL: deepLinks.permalink});
expect(baseProps.actions.setDeepLinkURL).toHaveBeenCalled();
expect(baseProps.actions.selectFocusedPostId).toHaveBeenCalled();
expect(baseProps.navigator.showModal).toHaveBeenCalled();
expect(baseProps.actions.showModalOverCurrentContext).toHaveBeenCalled();
expect(wrapper.getElement()).toMatchSnapshot();
});

View file

@ -16,23 +16,11 @@ exports[`PostTextBox should match, full snapshot 1`] = `
}
}
>
<AttachmentButton
<Connect(AttachmentButton)
blurTextBox={[Function]}
browseFileTypes="public.item"
canBrowseFiles={true}
canBrowsePhotoLibrary={true}
canBrowseVideoLibrary={true}
canTakePhoto={true}
canTakeVideo={true}
extraOptions={null}
fileCount={0}
maxFileCount={5}
maxFileSize={1024}
navigator={
Object {
"showModal": [MockFunction],
}
}
onShowFileMaxWarning={[Function]}
onShowFileSizeWarning={[Function]}
theme={
@ -64,7 +52,6 @@ exports[`PostTextBox should match, full snapshot 1`] = `
}
}
uploadFiles={[Function]}
validMimeTypes={Array []}
/>
<View
style={

View file

@ -38,9 +38,6 @@ describe('PostTextBox', () => {
files: [],
maxFileSize: 1024,
maxMessageLength: 4000,
navigator: {
showModal: jest.fn(),
},
rootId: '',
theme: Preferences.THEMES.default,
uploadFileRequestStatus: 'NOT_STARTED',

View file

@ -63,7 +63,6 @@ export default class PostTextBoxBase extends PureComponent {
files: PropTypes.array,
maxFileSize: PropTypes.number.isRequired,
maxMessageLength: PropTypes.number.isRequired,
navigator: PropTypes.object,
rootId: PropTypes.string,
theme: PropTypes.object.isRequired,
uploadFileRequestStatus: PropTypes.string.isRequired,
@ -189,7 +188,7 @@ export default class PostTextBoxBase extends PureComponent {
};
getAttachmentButton = () => {
const {canUploadFiles, channelIsReadOnly, files, maxFileSize, navigator, theme} = this.props;
const {canUploadFiles, channelIsReadOnly, files, maxFileSize, theme} = this.props;
let attachmentButton = null;
if (canUploadFiles && !channelIsReadOnly) {
@ -197,7 +196,6 @@ export default class PostTextBoxBase extends PureComponent {
<AttachmentButton
blurTextBox={this.blur}
theme={theme}
navigator={navigator}
fileCount={files.length}
maxFileSize={maxFileSize}
maxFileCount={MAX_FILE_COUNT}

View file

@ -10,16 +10,8 @@ import ProfilePictureButton from './profile_picture_button.js';
import {Client4} from 'mattermost-redux/client';
describe('profile_picture_button', () => {
const navigator = {
setOnNavigatorEvent: jest.fn(),
setButtons: jest.fn(),
dismissModal: jest.fn(),
push: jest.fn(),
};
const baseProps = {
theme: Preferences.THEMES.default,
navigator,
currentUser: {
first_name: 'Dwight',
last_name: 'Schrute',

View file

@ -13,6 +13,7 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
import {showModal, showModalOverCurrentContext} from 'app/actions/navigation';
import {addReaction} from 'app/actions/views/emoji';
import Reactions from './reactions';
@ -63,6 +64,8 @@ function mapDispatchToProps(dispatch) {
addReaction,
getReactionsForPost,
removeReaction,
showModal,
showModalOverCurrentContext,
}, dispatch),
};
}

View file

@ -23,9 +23,10 @@ export default class Reactions extends PureComponent {
addReaction: PropTypes.func.isRequired,
getReactionsForPost: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
showModal: PropTypes.func.isRequired,
showModalOverCurrentContext: PropTypes.func.isRequired,
}).isRequired,
currentUserId: PropTypes.string.isRequired,
navigator: PropTypes.object.isRequired,
position: PropTypes.oneOf(['right', 'left']),
postId: PropTypes.string.isRequired,
reactions: PropTypes.object,
@ -50,25 +51,18 @@ export default class Reactions extends PureComponent {
}
handleAddReaction = preventDoubleTap(() => {
const {actions, theme} = this.props;
const {formatMessage} = this.context.intl;
const {navigator, theme} = this.props;
const screen = 'AddReaction';
const title = formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'});
MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor).then((source) => {
navigator.showModal({
screen: 'AddReaction',
title: formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'}),
animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
closeButton: source,
onEmojiPress: this.handleAddReactionToPost,
},
});
const passProps = {
closeButton: source,
onEmojiPress: this.handleAddReactionToPost,
};
actions.showModal(screen, title, passProps);
});
});
@ -87,28 +81,18 @@ export default class Reactions extends PureComponent {
};
showReactionList = () => {
const {navigator, postId} = this.props;
const {actions, postId} = this.props;
const options = {
screen: 'ReactionList',
animationType: 'none',
backButtonTitle: '',
navigatorStyle: {
navBarHidden: true,
navBarTransparent: true,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
passProps: {
postId,
},
const screen = 'ReactionList';
const passProps = {
postId,
};
navigator.showModal(options);
actions.showModalOverCurrentContext(screen, passProps);
}
renderReactions = () => {
const {currentUserId, navigator, reactions, theme, postId} = this.props;
const {currentUserId, reactions, theme, postId} = this.props;
const highlightedReactions = [];
const reactionsByName = Object.values(reactions).reduce((acc, reaction) => {
if (acc.has(reaction.emoji_name)) {
@ -131,7 +115,6 @@ export default class Reactions extends PureComponent {
count={reactionsByName.get(r).length}
emojiName={r}
highlight={highlightedReactions.includes(r)}
navigator={navigator}
onPress={this.handleReactionPress}
onLongPress={this.showReactionList}
postId={postId}

View file

@ -1,13 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
import {getCurrentLocale} from 'app/selectors/i18n';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {resetToTeams} from 'app/actions/navigation';
import {getCurrentLocale} from 'app/selectors/i18n';
import {removeProtocol} from 'app/utils/url';
import Root from './root';
@ -17,10 +18,17 @@ function mapStateToProps(state) {
return {
theme: getTheme(state),
currentChannelId: getCurrentChannelId(state),
currentUrl: removeProtocol(getCurrentUrl(state)),
locale,
};
}
export default connect(mapStateToProps)(Root);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
resetToTeams,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Root);

View file

@ -9,15 +9,16 @@ import {Platform} from 'react-native';
import {Client4} from 'mattermost-redux/client';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {NavigationTypes, ViewTypes} from 'app/constants';
import {NavigationTypes} from 'app/constants';
import {getTranslations} from 'app/i18n';
export default class Root extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
resetToTeams: PropTypes.func.isRequired,
}).isRequired,
children: PropTypes.node,
navigator: PropTypes.object,
excludeEvents: PropTypes.bool,
currentChannelId: PropTypes.string,
currentUrl: PropTypes.string,
locale: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
@ -27,8 +28,6 @@ export default class Root extends PureComponent {
Client4.setAcceptLanguage(this.props.locale);
if (!this.props.excludeEvents) {
EventEmitter.on(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
EventEmitter.on(ViewTypes.NOTIFICATION_TAPPED, this.handleNotificationTapped);
EventEmitter.on(NavigationTypes.NAVIGATION_NO_TEAMS, this.handleNoTeams);
EventEmitter.on(NavigationTypes.NAVIGATION_ERROR_TEAMS, this.errorTeamsList);
}
@ -42,30 +41,11 @@ export default class Root extends PureComponent {
componentWillUnmount() {
if (!this.props.excludeEvents) {
EventEmitter.off(ViewTypes.NOTIFICATION_IN_APP, this.handleInAppNotification);
EventEmitter.off(ViewTypes.NOTIFICATION_TAPPED, this.handleNotificationTapped);
EventEmitter.off(NavigationTypes.NAVIGATION_NO_TEAMS, this.handleNoTeams);
EventEmitter.off(NavigationTypes.NAVIGATION_ERROR_TEAMS, this.errorTeamsList);
}
}
handleInAppNotification = (notification) => {
const {data} = notification;
const {currentChannelId, navigator} = this.props;
if (data && data.channel_id !== currentChannelId) {
navigator.showInAppNotification({
screen: 'Notification',
position: 'top',
autoDismissTimerSec: 5,
dismissWithSwipe: true,
passProps: {
notification,
},
});
}
};
handleNoTeams = () => {
if (!this.refs.provider) {
setTimeout(this.handleNoTeams, 200);
@ -83,28 +63,24 @@ export default class Root extends PureComponent {
}
navigateToTeamsPage = (screen) => {
const {currentUrl, navigator, theme} = this.props;
const {currentUrl, theme, actions} = this.props;
const {intl} = this.refs.provider.getChildContext();
let navigatorButtons;
let passProps = {theme};
const options = {topBar: {}};
if (Platform.OS === 'android') {
navigatorButtons = {
rightButtons: [{
title: intl.formatMessage({id: 'sidebar_right_menu.logout', defaultMessage: 'Logout'}),
id: 'logout',
buttonColor: theme.sidebarHeaderTextColor,
showAsAction: 'always',
}],
};
options.topBar.rightButtons = [{
id: 'logout',
text: intl.formatMessage({id: 'sidebar_right_menu.logout', defaultMessage: 'Logout'}),
color: theme.sidebarHeaderTextColor,
showAsAction: 'always',
}];
} else {
navigatorButtons = {
leftButtons: [{
title: intl.formatMessage({id: 'sidebar_right_menu.logout', defaultMessage: 'Logout'}),
id: 'logout',
buttonColor: theme.sidebarHeaderTextColor,
}],
};
options.topBar.leftButtons = [{
id: 'logout',
text: intl.formatMessage({id: 'sidebar_right_menu.logout', defaultMessage: 'Logout'}),
color: theme.sidebarHeaderTextColor,
}];
}
if (screen === 'SelectTeam') {
@ -115,34 +91,11 @@ export default class Root extends PureComponent {
};
}
navigator.resetTo({
screen,
title: intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}),
animated: false,
backButtonTitle: '',
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
navigatorButtons,
passProps,
});
const title = intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'});
actions.resetToTeams(screen, title, passProps, options);
}
handleNotificationTapped = async () => {
const {navigator} = this.props;
if (Platform.OS === 'android') {
navigator.dismissModal({animation: 'none'});
}
navigator.popToRoot({
animated: false,
});
};
render() {
const locale = this.props.locale;

View file

@ -23,7 +23,6 @@ export default class SafeAreaIos extends PureComponent {
forceTop: PropTypes.number,
keyboardOffset: PropTypes.number.isRequired,
navBarBackgroundColor: PropTypes.string,
navigator: PropTypes.object,
headerComponent: PropTypes.node,
theme: PropTypes.object.isRequired,
};
@ -35,10 +34,6 @@ export default class SafeAreaIos extends PureComponent {
constructor(props) {
super(props);
if (props.navigator) {
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
}
this.state = {
keyboard: false,
safeAreaInsets: {
@ -110,15 +105,6 @@ export default class SafeAreaIos extends PureComponent {
this.setState({keyboard: true});
};
onNavigatorEvent = (event) => {
switch (event.id) {
case 'willAppear':
case 'didDisappear':
this.getSafeAreaInsets();
break;
}
};
renderTopBar = () => {
const {safeAreaInsets, statusBarHeight} = this.state;
const {headerComponent, excludeHeader, forceTop, navBarBackgroundColor, theme} = this.props;

View file

@ -2,11 +2,10 @@
exports[`ChannelItem should match snapshot 1`] = `
<AnimatedComponent>
<TouchableHighlight
activeOpacity={0.85}
delayPressOut={100}
onLongPress={[Function]}
<TouchablePreview
onPress={[Function]}
onPressIn={[Function]}
touchableComponent={[Function]}
underlayColor="rgba(69,120,191,0.5)"
>
<View
@ -98,17 +97,16 @@ exports[`ChannelItem should match snapshot 1`] = `
</Text>
</View>
</View>
</TouchableHighlight>
</TouchablePreview>
</AnimatedComponent>
`;
exports[`ChannelItem should match snapshot for current user i.e currentUser (you) 1`] = `
<AnimatedComponent>
<TouchableHighlight
activeOpacity={0.85}
delayPressOut={100}
onLongPress={[Function]}
<TouchablePreview
onPress={[Function]}
onPressIn={[Function]}
touchableComponent={[Function]}
underlayColor="rgba(69,120,191,0.5)"
>
<View
@ -211,17 +209,16 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
</Text>
</View>
</View>
</TouchableHighlight>
</TouchablePreview>
</AnimatedComponent>
`;
exports[`ChannelItem should match snapshot for current user i.e currentUser (you) when isSearchResult 1`] = `
<AnimatedComponent>
<TouchableHighlight
activeOpacity={0.85}
delayPressOut={100}
onLongPress={[Function]}
<TouchablePreview
onPress={[Function]}
onPressIn={[Function]}
touchableComponent={[Function]}
underlayColor="rgba(69,120,191,0.5)"
>
<View
@ -324,17 +321,16 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
</Text>
</View>
</View>
</TouchableHighlight>
</TouchablePreview>
</AnimatedComponent>
`;
exports[`ChannelItem should match snapshot for deactivated user and is currentChannel 1`] = `
<AnimatedComponent>
<TouchableHighlight
activeOpacity={0.85}
delayPressOut={100}
onLongPress={[Function]}
<TouchablePreview
onPress={[Function]}
onPressIn={[Function]}
touchableComponent={[Function]}
underlayColor="rgba(69,120,191,0.5)"
>
<View
@ -437,17 +433,16 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
</Text>
</View>
</View>
</TouchableHighlight>
</TouchablePreview>
</AnimatedComponent>
`;
exports[`ChannelItem should match snapshot for deactivated user and is searchResult 1`] = `
<AnimatedComponent>
<TouchableHighlight
activeOpacity={0.85}
delayPressOut={100}
onLongPress={[Function]}
<TouchablePreview
onPress={[Function]}
onPressIn={[Function]}
touchableComponent={[Function]}
underlayColor="rgba(69,120,191,0.5)"
>
<View
@ -539,7 +534,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
</Text>
</View>
</View>
</TouchableHighlight>
</TouchablePreview>
</AnimatedComponent>
`;
@ -551,11 +546,10 @@ exports[`ChannelItem should match snapshot for showUnreadForMsgs 1`] = `null`;
exports[`ChannelItem should match snapshot with draft 1`] = `
<AnimatedComponent>
<TouchableHighlight
activeOpacity={0.85}
delayPressOut={100}
onLongPress={[Function]}
<TouchablePreview
onPress={[Function]}
onPressIn={[Function]}
touchableComponent={[Function]}
underlayColor="rgba(69,120,191,0.5)"
>
<View
@ -647,17 +641,16 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
</Text>
</View>
</View>
</TouchableHighlight>
</TouchablePreview>
</AnimatedComponent>
`;
exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
<AnimatedComponent>
<TouchableHighlight
activeOpacity={0.85}
delayPressOut={100}
onLongPress={[Function]}
<TouchablePreview
onPress={[Function]}
onPressIn={[Function]}
touchableComponent={[Function]}
underlayColor="rgba(69,120,191,0.5)"
>
<View
@ -775,6 +768,6 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
/>
</View>
</View>
</TouchableHighlight>
</TouchablePreview>
</AnimatedComponent>
`;

View file

@ -2,17 +2,17 @@
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import {General} from 'mattermost-redux/constants';
import PropTypes from 'prop-types';
import {
Animated,
Platform,
TouchableHighlight,
Text,
View,
} from 'react-native';
import {intlShape} from 'react-intl';
import {Navigation} from 'react-native-navigation';
import {General} from 'mattermost-redux/constants';
import Badge from 'app/components/badge';
import ChannelIcon from 'app/components/channel_icon';
@ -32,7 +32,6 @@ export default class ChannelItem extends PureComponent {
isUnread: PropTypes.bool,
hasDraft: PropTypes.bool,
mentions: PropTypes.number.isRequired,
navigator: PropTypes.object,
onSelectChannel: PropTypes.func.isRequired,
shouldHideChannel: PropTypes.bool,
showUnreadForMsgs: PropTypes.bool.isRequired,
@ -40,6 +39,7 @@ export default class ChannelItem extends PureComponent {
unreadMsgs: PropTypes.number.isRequired,
isSearchResult: PropTypes.bool,
isBot: PropTypes.bool.isRequired,
previewChannel: PropTypes.func,
};
static defaultProps = {
@ -58,28 +58,25 @@ export default class ChannelItem extends PureComponent {
});
});
onPreview = () => {
const {channelId, navigator} = this.props;
if (Platform.OS === 'ios' && navigator && this.previewRef) {
onPreview = ({reactTag}) => {
const {channelId, previewChannel} = this.props;
if (previewChannel) {
const {intl} = this.context;
navigator.push({
screen: 'ChannelPeek',
previewCommit: false,
previewView: this.previewRef,
previewActions: [{
id: 'action-mark-as-read',
title: intl.formatMessage({id: 'mobile.channel.markAsRead', defaultMessage: 'Mark As Read'}),
}],
passProps: {
channelId,
const passProps = {
channelId,
};
const options = {
preview: {
reactTag,
actions: [{
id: 'action-mark-as-read',
title: intl.formatMessage({id: 'mobile.channel.markAsRead', defaultMessage: 'Mark As Read'}),
}],
},
});
}
};
};
setPreviewRef = (ref) => {
this.previewRef = ref;
previewChannel(passProps, options);
}
};
showChannelAsUnread = () => {
@ -190,11 +187,12 @@ export default class ChannelItem extends PureComponent {
);
return (
<AnimatedView ref={this.setPreviewRef}>
<TouchableHighlight
<AnimatedView>
<Navigation.TouchablePreview
touchableComponent={TouchableHighlight}
underlayColor={changeOpacity(theme.sidebarTextHoverBg, 0.5)}
onPress={this.onPress}
onLongPress={this.onPreview}
onPressIn={this.onPreview}
>
<View style={[style.container, mutedStyle]}>
{extraBorder}
@ -210,7 +208,7 @@ export default class ChannelItem extends PureComponent {
{badge}
</View>
</View>
</TouchableHighlight>
</Navigation.TouchablePreview>
</AnimatedView>
);
}

View file

@ -3,7 +3,7 @@
import React from 'react';
import {shallow} from 'enzyme';
import {TouchableHighlight} from 'react-native';
import {Navigation} from 'react-native-navigation';
import Preferences from 'mattermost-redux/constants/preferences';
@ -31,7 +31,6 @@ describe('ChannelItem', () => {
isUnread: true,
hasDraft: false,
mentions: 0,
navigator: {push: () => {}}, // eslint-disable-line no-empty-function
onSelectChannel: () => {}, // eslint-disable-line no-empty-function
shouldHideChannel: false,
showUnreadForMsgs: true,
@ -216,7 +215,7 @@ describe('ChannelItem', () => {
{context: {intl: {formatMessage: jest.fn()}}},
);
wrapper.find(TouchableHighlight).simulate('press');
wrapper.find(Navigation.TouchablePreview).simulate('press');
jest.runAllTimers();
const expectedChannelParams = {id: baseProps.channelId, display_name: baseProps.displayName, fake: channel.fake, type: channel.type};

View file

@ -22,7 +22,6 @@ let FilteredList = null;
export default class ChannelsList extends PureComponent {
static propTypes = {
navigator: PropTypes.object,
onJoinChannel: PropTypes.func.isRequired,
onSearchEnds: PropTypes.func.isRequired,
onSearchStart: PropTypes.func.isRequired,
@ -30,6 +29,7 @@ export default class ChannelsList extends PureComponent {
onShowTeams: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
drawerOpened: PropTypes.bool,
previewChannel: PropTypes.func,
};
static contextTypes = {
@ -86,9 +86,9 @@ export default class ChannelsList extends PureComponent {
render() {
const {intl} = this.context;
const {
navigator,
onShowTeams,
theme,
previewChannel,
} = this.props;
const {searching, term} = this.state;
@ -101,14 +101,15 @@ export default class ChannelsList extends PureComponent {
onSelectChannel={this.onSelectChannel}
styles={styles}
term={term}
previewChannel={previewChannel}
/>
);
} else {
list = (
<List
navigator={navigator}
onSelectChannel={this.onSelectChannel}
styles={styles}
previewChannel={previewChannel}
/>
);
}

View file

@ -54,6 +54,7 @@ class FilteredList extends Component {
styles: PropTypes.object.isRequired,
term: PropTypes.string,
theme: PropTypes.object.isRequired,
previewChannel: PropTypes.func,
};
static defaultProps = {
@ -126,6 +127,7 @@ class FilteredList extends Component {
isUnread={channel.isUnread}
mentions={0}
onSelectChannel={this.onSelectChannel}
previewChannel={this.props.previewChannel}
/>
);
};

View file

@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {General} from 'mattermost-redux/constants';
@ -17,6 +18,8 @@ import {memoizeResult} from 'mattermost-redux/utils/helpers';
import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {showModal} from 'app/actions/navigation';
import {DeviceTypes, ViewTypes} from 'app/constants';
import List from './list';
@ -67,6 +70,14 @@ function mapStateToProps(state) {
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
showModal,
}, dispatch),
};
}
function areStatesEqual(next, prev) {
const equalRoles = getCurrentUserRoles(prev) === getCurrentUserRoles(next);
const equalChannels = next.entities.channels === prev.entities.channels;
@ -77,4 +88,4 @@ function areStatesEqual(next, prev) {
return equalChannels && equalConfig && equalRoles && equalUsers && equalFav;
}
export default connect(mapStateToProps, null, null, {pure: true, areStatesEqual})(List);
export default connect(mapStateToProps, mapDispatchToProps, null, {pure: true, areStatesEqual})(List);

View file

@ -34,14 +34,17 @@ let UnreadIndicator = null;
export default class List extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
showModal: PropTypes.func.isRequired,
}).isRequired,
canCreatePrivateChannels: PropTypes.bool.isRequired,
favoriteChannelIds: PropTypes.array.isRequired,
navigator: PropTypes.object,
onSelectChannel: PropTypes.func.isRequired,
unreadChannelIds: PropTypes.array.isRequired,
styles: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
orderedChannelIds: PropTypes.array.isRequired,
previewChannel: PropTypes.func,
};
static contextTypes = {
@ -159,7 +162,10 @@ export default class List extends PureComponent {
};
showCreateChannelOptions = () => {
const {canCreatePrivateChannels, navigator} = this.props;
const {
canCreatePrivateChannels,
actions,
} = this.props;
const items = [];
const moreChannels = {
@ -197,117 +203,87 @@ export default class List extends PureComponent {
}
items.push(newConversation);
navigator.showModal({
screen: 'OptionsModal',
title: '',
animationType: 'none',
passProps: {
items,
onItemPress: () => navigator.dismissModal({
animationType: 'none',
}),
const screen = 'OptionsModal';
const title = '';
const passProps = {
items,
};
const options = {
modalPresentationStyle: 'overCurrentContext',
layout: {
backgroundColor: 'transparent',
},
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
topBar: {
visible: false,
height: 0,
},
});
animations: {
showModal: {
enable: false,
},
dismissModal: {
enable: false,
},
},
};
actions.showModal(screen, title, passProps, options);
};
goToCreatePublicChannel = preventDoubleTap(() => {
const {navigator, theme} = this.props;
const {actions} = this.props;
const {intl} = this.context;
const screen = 'CreateChannel';
const title = intl.formatMessage({id: 'mobile.create_channel.public', defaultMessage: 'New Public Channel'});
const passProps = {
channelType: General.OPEN_CHANNEL,
closeButton: this.closeButton,
};
navigator.showModal({
screen: 'CreateChannel',
animationType: 'slide-up',
title: intl.formatMessage({id: 'mobile.create_channel.public', defaultMessage: 'New Public Channel'}),
backButtonTitle: '',
animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
channelType: General.OPEN_CHANNEL,
closeButton: this.closeButton,
},
});
actions.showModal(screen, title, passProps);
});
goToCreatePrivateChannel = preventDoubleTap(() => {
const {navigator, theme} = this.props;
const {actions} = this.props;
const {intl} = this.context;
const screen = 'CreateChannel';
const title = intl.formatMessage({id: 'mobile.create_channel.private', defaultMessage: 'New Private Channel'});
const passProps = {
channelType: General.PRIVATE_CHANNEL,
closeButton: this.closeButton,
};
navigator.showModal({
screen: 'CreateChannel',
animationType: 'slide-up',
title: intl.formatMessage({id: 'mobile.create_channel.private', defaultMessage: 'New Private Channel'}),
backButtonTitle: '',
animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
channelType: General.PRIVATE_CHANNEL,
closeButton: this.closeButton,
},
});
actions.showModal(screen, title, passProps);
});
goToDirectMessages = preventDoubleTap(() => {
const {navigator, theme} = this.props;
const {actions} = this.props;
const {intl} = this.context;
navigator.showModal({
screen: 'MoreDirectMessages',
title: intl.formatMessage({id: 'mobile.more_dms.title', defaultMessage: 'New Conversation'}),
animationType: 'slide-up',
animated: true,
backButtonTitle: '',
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
navigatorButtons: {
const screen = 'MoreDirectMessages';
const title = intl.formatMessage({id: 'mobile.more_dms.title', defaultMessage: 'New Conversation'});
const passProps = {};
const options = {
topBar: {
leftButtons: [{
id: 'close-dms',
icon: this.closeButton,
}],
},
});
};
actions.showModal(screen, title, passProps, options);
});
goToMoreChannels = preventDoubleTap(() => {
const {navigator, theme} = this.props;
const {actions} = this.props;
const {intl} = this.context;
const screen = 'MoreChannels';
const title = intl.formatMessage({id: 'more_channels.title', defaultMessage: 'More Channels'});
const passProps = {
closeButton: this.closeButton,
};
navigator.showModal({
screen: 'MoreChannels',
animationType: 'slide-up',
title: intl.formatMessage({id: 'more_channels.title', defaultMessage: 'More Channels'}),
backButtonTitle: '',
animated: true,
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
passProps: {
closeButton: this.closeButton,
},
});
actions.showModal(screen, title, passProps);
});
keyExtractor = (item) => item.id || item;
@ -349,15 +325,15 @@ export default class List extends PureComponent {
};
renderItem = ({item}) => {
const {favoriteChannelIds, unreadChannelIds} = this.props;
const {favoriteChannelIds, unreadChannelIds, previewChannel} = this.props;
return (
<ChannelItem
channelId={item}
isUnread={unreadChannelIds.includes(item)}
isFavorite={favoriteChannelIds.includes(item)}
navigator={this.props.navigator}
onSelectChannel={this.onSelectChannel}
previewChannel={previewChannel}
/>
);
};

View file

@ -46,9 +46,9 @@ export default class ChannelSidebar extends Component {
currentUserId: PropTypes.string.isRequired,
deviceWidth: PropTypes.number.isRequired,
isLandscape: PropTypes.bool.isRequired,
navigator: PropTypes.object,
teamsCount: PropTypes.number.isRequired,
theme: PropTypes.object.isRequired,
previewChannel: PropTypes.func,
};
static contextTypes = {
@ -143,7 +143,7 @@ export default class ChannelSidebar extends Component {
};
handleShowDrawerContent = () => {
this.setState({show: true});
requestAnimationFrame(() => this.setState({show: true}));
};
closeChannelDrawer = () => {
@ -315,9 +315,9 @@ export default class ChannelSidebar extends Component {
renderNavigationView = (drawerWidth) => {
const {
navigator,
teamsCount,
theme,
previewChannel,
} = this.props;
const {
@ -349,7 +349,6 @@ export default class ChannelSidebar extends Component {
>
<TeamsList
closeChannelDrawer={this.closeChannelDrawer}
navigator={navigator}
/>
</View>
);
@ -363,7 +362,6 @@ export default class ChannelSidebar extends Component {
>
<ChannelsList
ref={this.channelListRef}
navigator={navigator}
onSelectChannel={this.selectChannel}
onJoinChannel={this.joinChannel}
onShowTeams={this.showTeams}
@ -371,6 +369,7 @@ export default class ChannelSidebar extends Component {
onSearchEnds={this.onSearchEnds}
theme={theme}
drawerOpened={this.state.drawerOpened}
previewChannel={previewChannel}
/>
</View>
);
@ -380,7 +379,6 @@ export default class ChannelSidebar extends Component {
navBarBackgroundColor={theme.sidebarBg}
backgroundColor={theme.sidebarHeaderBg}
footerColor={theme.sidebarHeaderBg}
navigator={navigator}
>
<DrawerSwiper
ref={this.drawerSwiperRef}

View file

@ -8,6 +8,7 @@ import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
import {getCurrentTeamId, getMySortedTeamIds, getJoinableTeamIds} from 'mattermost-redux/selectors/entities/teams';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {showModal} from 'app/actions/navigation';
import {handleTeamChange} from 'app/actions/views/select_team';
import {getCurrentLocale} from 'app/selectors/i18n';
import {removeProtocol} from 'app/utils/url';
@ -30,6 +31,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
handleTeamChange,
showModal,
}, dispatch),
};
}

View file

@ -33,12 +33,12 @@ export default class TeamsList extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
handleTeamChange: PropTypes.func.isRequired,
showModal: PropTypes.func.isRequired,
}).isRequired,
closeChannelDrawer: PropTypes.func.isRequired,
currentTeamId: PropTypes.string.isRequired,
currentUrl: PropTypes.string.isRequired,
hasOtherJoinableTeams: PropTypes.bool,
navigator: PropTypes.object.isRequired,
teamIds: PropTypes.array.isRequired,
theme: PropTypes.object.isRequired,
};
@ -76,31 +76,23 @@ export default class TeamsList extends PureComponent {
goToSelectTeam = preventDoubleTap(() => {
const {intl} = this.context;
const {currentUrl, navigator, theme} = this.props;
navigator.showModal({
screen: 'SelectTeam',
title: intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'}),
animationType: 'slide-up',
animated: true,
backButtonTitle: '',
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
navigatorButtons: {
const {currentUrl, theme, actions} = this.props;
const screen = 'SelectTeam';
const title = intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'});
const passProps = {
currentUrl,
theme,
};
const options = {
topBar: {
leftButtons: [{
id: 'close-teams',
icon: this.closeButton,
}],
},
passProps: {
currentUrl,
theme,
},
});
};
actions.showModal(screen, title, passProps, options);
});
keyExtractor = (item) => {

View file

@ -8,6 +8,12 @@ import {logout, setStatus} from 'mattermost-redux/actions/users';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUser, getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
import {
showModal,
showModalOverCurrentContext,
dismissModal,
} from 'app/actions/navigation';
import {isLandscape, getDimensions} from 'app/selectors/device';
import SettingsSidebar from './settings_sidebar';
@ -30,6 +36,9 @@ function mapDispatchToProps(dispatch) {
actions: bindActionCreators({
logout,
setStatus,
showModal,
showModalOverCurrentContext,
dismissModal,
}, dispatch),
};
}

View file

@ -37,13 +37,15 @@ export default class SettingsDrawer extends PureComponent {
actions: PropTypes.shape({
logout: PropTypes.func.isRequired,
setStatus: PropTypes.func.isRequired,
showModal: PropTypes.func.isRequired,
showModalOverCurrentContext: PropTypes.func.isRequired,
dismissModal: PropTypes.func.isRequired,
}).isRequired,
blurPostTextBox: PropTypes.func.isRequired,
children: PropTypes.node,
currentUser: PropTypes.object.isRequired,
deviceWidth: PropTypes.number.isRequired,
isLandscape: PropTypes.bool.isRequired,
navigator: PropTypes.object,
status: PropTypes.string,
theme: PropTypes.object.isRequired,
};
@ -66,10 +68,12 @@ export default class SettingsDrawer extends PureComponent {
}
componentDidMount() {
EventEmitter.on('close_settings_sidebar', this.closeSettingsSidebar);
BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
}
componentWillUnmount() {
EventEmitter.off('close_settings_sidebar', this.closeSettingsSidebar);
BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
}
@ -107,6 +111,7 @@ export default class SettingsDrawer extends PureComponent {
};
handleSetStatus = preventDoubleTap(() => {
const {actions} = this.props;
const items = [{
action: () => this.setStatus(General.ONLINE),
text: {
@ -133,21 +138,7 @@ export default class SettingsDrawer extends PureComponent {
},
}];
this.props.navigator.showModal({
screen: 'OptionsModal',
title: '',
animationType: 'none',
passProps: {
items,
},
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false,
screenBackgroundColor: 'transparent',
modalPresentationStyle: 'overCurrentContext',
},
});
actions.showModalOverCurrentContext('OptionsModal', {items});
});
goToEditProfile = preventDoubleTap(() => {
@ -207,31 +198,20 @@ export default class SettingsDrawer extends PureComponent {
});
openModal = (screen, title, passProps) => {
const {navigator, theme} = this.props;
this.closeSettingsSidebar();
const {actions} = this.props;
const options = {
topBar: {
leftButtons: [{
id: 'close-settings',
icon: this.closeButton,
}],
},
};
InteractionManager.runAfterInteractions(() => {
navigator.showModal({
screen,
title,
animationType: 'slide-up',
animated: true,
backButtonTitle: '',
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
navigatorButtons: {
leftButtons: [{
id: 'close-settings',
icon: this.closeButton,
}],
},
passProps,
});
actions.showModal(screen, title, passProps, options);
});
};
@ -251,7 +231,7 @@ export default class SettingsDrawer extends PureComponent {
};
renderNavigationView = () => {
const {currentUser, navigator, theme} = this.props;
const {currentUser, theme} = this.props;
const style = getStyleSheet(theme);
return (
@ -261,7 +241,6 @@ export default class SettingsDrawer extends PureComponent {
footerColor={theme.centerChannelBg}
footerComponent={<View style={style.container}/>}
headerComponent={<View style={style.container}/>}
navigator={navigator}
theme={theme}
>
<View style={style.container}>
@ -356,12 +335,10 @@ export default class SettingsDrawer extends PureComponent {
};
setStatus = (status) => {
const {status: currentUserStatus, navigator} = this.props;
const {status: currentUserStatus, actions} = this.props;
if (currentUserStatus === General.OUT_OF_OFFICE) {
navigator.dismissModal({
animationType: 'none',
});
actions.dismissModal();
this.closeSettingsSidebar();
this.confirmReset(status);
return;

View file

@ -9,6 +9,7 @@ const NavigationTypes = keyMirror({
NAVIGATION_NO_TEAMS: null,
RESTART_APP: null,
NAVIGATION_ERROR_TEAMS: null,
NAVIGATION_SHOW_OVERLAY: null,
});
export default NavigationTypes;

View file

@ -42,7 +42,6 @@ const ViewTypes = keyMirror({
COMMENT_DRAFT_SELECTION_CHANGED: null,
NOTIFICATION_IN_APP: null,
NOTIFICATION_TAPPED: null,
SET_POST_DRAFT: null,
SET_COMMENT_DRAFT: null,
@ -87,9 +86,6 @@ const ViewTypes = keyMirror({
INCREMENT_EMOJI_PICKER_PAGE: null,
LAUNCH_LOGIN: null,
LAUNCH_CHANNEL: null,
SET_DEEP_LINK_URL: null,
SET_PROFILE_IMAGE_URI: null,

View file

@ -7,7 +7,7 @@ import * as KeyChain from 'react-native-keychain';
import {Client4} from 'mattermost-redux/client';
import mattermostManaged from 'app/mattermost_managed';
import ephemeralStore from 'app/store/ephemeral_store';
import EphemeralStore from 'app/store/ephemeral_store';
import {setCSRFFromCookie} from 'app/utils/security';
const CURRENT_SERVER = '@currentServerUrl';
@ -26,7 +26,7 @@ export const setAppCredentials = (deviceToken, currentUserId, token, url) => {
try {
const username = `${deviceToken}, ${currentUserId}`;
ephemeralStore.deviceToken = deviceToken;
EphemeralStore.deviceToken = deviceToken;
AsyncStorage.setItem(CURRENT_SERVER, url);
KeyChain.setInternetCredentials(url, username, token, {accessGroup: mattermostManaged.appGroupIdentifier});
} catch (e) {
@ -111,7 +111,7 @@ async function getInternetCredentials(url) {
const [deviceToken, currentUserId] = usernameParsed;
if (token && token !== 'undefined') {
ephemeralStore.deviceToken = deviceToken;
EphemeralStore.deviceToken = deviceToken;
Client4.setUserId(currentUserId);
Client4.setUrl(url);
Client4.setToken(token);

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