Cold startup js refactor (#1598)
* Switch to SingleDex and remove all locales * WIP Mattermost Start Component for lazy loading modules * Add files changed for native modules * Add Entry component and app global object * dispatch setStatusBarHeight for iOS * Update screen imports * Include Entry screen * Refactor app to mattermost.android.js * Override unnecessary java files * Fix minor issues in changes * Display empty state based on user credentials Also, add proper background theme for empty loading screen * Add native module constant cache support * Fix startup theme regression * Add Keychain support for credentials * Fix Orientation regression * Fix SharedExtension regression * Emit NATIVE_APP_LAUNCHED across bridge only once during cold start * Add iOS Support * Revert to previous implementation of i18n * Fix styling issues * Include listener for SERVER_VERSION_CHANGED * Add SafeAreaView in Entry screen * Register deviceToken early, in order to get iOS PN Support * Include StartTimeModule * Add ReplyFromPush support and remove NATIVE_APP_LAUNCHED listener * Package native constants in StartTimeModule and avoid bridge calls * Fix check-style errors * Code cleanup * Rename StartTimeModule to InitializationModule * Remove NavigationApplication * Documentation and minor changes * Account for app opening after SharedExtension * Refactor getIntl to getTranslations * Move native module constants into it's own forked repos * Include FetchBlob and DeviceInfo forked repos
This commit is contained in:
parent
21f2624bec
commit
e8712f9199
30 changed files with 1883 additions and 702 deletions
2
Makefile
2
Makefile
|
|
@ -70,6 +70,7 @@ post-install:
|
|||
@cp ./native_modules/ImagePickerModule.java node_modules/react-native-image-picker/android/src/main/java/com/imagepicker
|
||||
@# Need to copy custom RNDocumentPicker.m that implements direct access to the document picker in iOS
|
||||
@cp ./native_modules/RNDocumentPicker.m node_modules/react-native-document-picker/ios/RNDocumentPicker/RNDocumentPicker.m
|
||||
|
||||
@rm -f node_modules/intl/.babelrc
|
||||
@# Hack to get react-intl and its dependencies to work with react-native
|
||||
@# Based off of https://github.com/este/este/blob/master/gulp/native-fix.js
|
||||
|
|
@ -78,6 +79,7 @@ post-install:
|
|||
@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|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_FULL_USER);|g' node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/params/Orientation.java
|
||||
@sed -i'' -e "s|activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);|activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);|g" node_modules/react-native-orientation/android/src/main/java/com/github/yamill/orientation/OrientationModule.java
|
||||
@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
|
||||
@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; \
|
||||
|
|
|
|||
|
|
@ -113,7 +113,6 @@ android {
|
|||
targetSdkVersion 23
|
||||
versionCode 103
|
||||
versionName "1.8.0"
|
||||
multiDexEnabled true
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
}
|
||||
|
|
@ -169,6 +168,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
compile project(':react-native-document-picker')
|
||||
compile project(':react-native-keychain')
|
||||
compile project(':react-native-doc-viewer')
|
||||
compile project(':react-native-video')
|
||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||
|
|
|
|||
|
|
@ -0,0 +1,154 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.app.Application;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.mattermost.rnbeta.react_native_interface.AsyncStorageHelper;
|
||||
import com.mattermost.rnbeta.react_native_interface.KeysReadableArray;
|
||||
import com.mattermost.rnbeta.react_native_interface.ResolvePromise;
|
||||
import com.oblador.keychain.KeychainModule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class InitializationModule extends ReactContextBaseJavaModule {
|
||||
|
||||
static final String TOOLBAR_BACKGROUND = "TOOLBAR_BACKGROUND";
|
||||
static final String TOOLBAR_TEXT_COLOR = "TOOLBAR_TEXT_COLOR";
|
||||
static final String APP_BACKGROUND = "APP_BACKGROUND";
|
||||
static final String DEVICE_SECURE_CACHE_KEY = "DEVICE_SECURE_CACHE_KEY";
|
||||
|
||||
private final Application mApplication;
|
||||
|
||||
public InitializationModule(Application application, ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mApplication = application;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Initialization";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
Map<String, Object> constants = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Package all native module variables in constants
|
||||
* in order to avoid the native bridge
|
||||
*
|
||||
* KeyStore:
|
||||
* credentialsExist
|
||||
* deviceToken
|
||||
* currentUserId
|
||||
* token
|
||||
* url
|
||||
*
|
||||
* AsyncStorage:
|
||||
* toolbarBackground
|
||||
* toolbarTextColor
|
||||
* appBackground
|
||||
* isDeviceSecure
|
||||
*
|
||||
* Miscellaneous:
|
||||
* MattermostManaged.Config
|
||||
* replyFromPushNotification
|
||||
*/
|
||||
|
||||
MainApplication app = (MainApplication) mApplication;
|
||||
final Boolean[] credentialsExist = {false};
|
||||
final WritableMap[] credentials = {null};
|
||||
final Object[] config = {null};
|
||||
|
||||
// Get KeyStore credentials
|
||||
KeychainModule module = new KeychainModule(this.getReactApplicationContext());
|
||||
module.getGenericPasswordForOptions(null, new ResolvePromise() {
|
||||
@Override
|
||||
public void resolve(@Nullable Object value) {
|
||||
if (value instanceof Boolean && !(Boolean)value) {
|
||||
credentialsExist[0] = false;
|
||||
return;
|
||||
}
|
||||
|
||||
WritableMap map = (WritableMap) value;
|
||||
if (map != null) {
|
||||
credentialsExist[0] = true;
|
||||
credentials[0] = map;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Get managedConfig from MattermostManagedModule
|
||||
MattermostManagedModule.getInstance().getConfig(new ResolvePromise() {
|
||||
@Override
|
||||
public void resolve(@Nullable Object value) {
|
||||
WritableNativeMap nativeMap = (WritableNativeMap) value;
|
||||
config[0] = value;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Get AsyncStorage key/values
|
||||
final ArrayList<String> keys = new ArrayList<String>(5);
|
||||
keys.add(TOOLBAR_BACKGROUND);
|
||||
keys.add(TOOLBAR_TEXT_COLOR);
|
||||
keys.add(APP_BACKGROUND);
|
||||
keys.add(DEVICE_SECURE_CACHE_KEY);
|
||||
KeysReadableArray asyncStorageKeys = new KeysReadableArray() {
|
||||
@Override
|
||||
public int size() {
|
||||
return keys.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int index) {
|
||||
return keys.get(index);
|
||||
}
|
||||
};
|
||||
|
||||
AsyncStorageHelper asyncStorage = new AsyncStorageHelper(this.getReactApplicationContext());
|
||||
HashMap<String, String> asyncStorageResults = asyncStorage.multiGet(asyncStorageKeys);
|
||||
|
||||
String toolbarBackground = asyncStorageResults.get(TOOLBAR_BACKGROUND);
|
||||
String toolbarTextColor = asyncStorageResults.get(TOOLBAR_TEXT_COLOR);
|
||||
String appBackground = asyncStorageResults.get(APP_BACKGROUND);
|
||||
String managedConfigResult = asyncStorageResults.get(DEVICE_SECURE_CACHE_KEY);
|
||||
|
||||
if (toolbarBackground != null
|
||||
&& toolbarTextColor != null
|
||||
&& appBackground != null) {
|
||||
|
||||
constants.put("themesExist", true);
|
||||
constants.put("toolbarBackground", toolbarBackground);
|
||||
constants.put("toolbarTextColor", toolbarTextColor);
|
||||
constants.put("appBackground", appBackground);
|
||||
} else {
|
||||
constants.put("themesExist", false);
|
||||
}
|
||||
|
||||
if (managedConfigResult != null) {
|
||||
constants.put("managedConfigResult", managedConfigResult);
|
||||
}
|
||||
|
||||
|
||||
if (credentialsExist[0]) {
|
||||
constants.put("credentialsExist", true);
|
||||
constants.put("credentials", credentials[0]);
|
||||
} else {
|
||||
constants.put("credentialsExist", false);
|
||||
}
|
||||
|
||||
constants.put("managedConfig", config[0]);
|
||||
constants.put("replyFromPushNotification", app.replyFromPushNotification);
|
||||
app.replyFromPushNotification = false;
|
||||
|
||||
return constants;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class InitializationPackage implements ReactPackage {
|
||||
|
||||
private final Application mApplication;
|
||||
|
||||
public InitializationPackage(Application application) {
|
||||
mApplication = application;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
return Arrays.<NativeModule>asList(new InitializationModule(mApplication, reactContext));
|
||||
}
|
||||
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import com.mattermost.share.SharePackage;
|
||||
import android.app.Application;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.reactnativedocumentpicker.ReactNativeDocumentPicker;
|
||||
import com.oblador.keychain.KeychainPackage;
|
||||
import com.reactlibrary.RNReactNativeDocViewerPackage;
|
||||
import com.brentvatne.react.ReactVideoPackage;
|
||||
import com.horcrux.svg.SvgPackage;
|
||||
|
|
@ -17,10 +17,8 @@ import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerPackage;
|
|||
import com.RNFetchBlob.RNFetchBlobPackage;
|
||||
import com.gantix.JailMonkey.JailMonkeyPackage;
|
||||
import io.tradle.react.LocalAuthPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
import com.imagepicker.ImagePickerPackage;
|
||||
|
|
@ -43,6 +41,8 @@ import java.util.List;
|
|||
|
||||
public class MainApplication extends NavigationApplication implements INotificationsApplication {
|
||||
public NotificationsLifecycleFacade notificationsLifecycleFacade;
|
||||
public Boolean sharedExtensionIsOpened = false;
|
||||
public Boolean replyFromPushNotification = false;
|
||||
|
||||
@Override
|
||||
public boolean isDebug() {
|
||||
|
|
@ -74,7 +74,9 @@ public class MainApplication extends NavigationApplication implements INotificat
|
|||
new ReactVideoPackage(),
|
||||
new RNReactNativeDocViewerPackage(),
|
||||
new ReactNativeDocumentPicker(),
|
||||
new SharePackage()
|
||||
new SharePackage(this),
|
||||
new KeychainPackage(),
|
||||
new InitializationPackage(this)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ public class NotificationReplyService extends HeadlessJsTaskService {
|
|||
int notificationId = intent.getIntExtra(CustomPushNotification.NOTIFICATION_ID, -1);
|
||||
CustomPushNotification.clearNotification(mContext, notificationId, channelId);
|
||||
|
||||
MainApplication app = (MainApplication) this.getApplication();
|
||||
app.replyFromPushNotification = true;
|
||||
Log.i("ReactNative", "Replying service");
|
||||
return new HeadlessJsTaskConfig(
|
||||
"notificationReplied",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
package com.mattermost.rnbeta.react_native_interface;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.modules.storage.ReactDatabaseSupplier;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* AsyncStorageHelper: Class that accesses React Native AsyncStorage Database synchronously
|
||||
*/
|
||||
public class AsyncStorageHelper {
|
||||
|
||||
// Static variables from: com.facebook.react.modules.storage.ReactDatabaseSupplier
|
||||
static final String TABLE_CATALYST = "catalystLocalStorage";
|
||||
static final String KEY_COLUMN = "key";
|
||||
static final String VALUE_COLUMN = "value";
|
||||
|
||||
|
||||
private static final int MAX_SQL_KEYS = 999;
|
||||
|
||||
Context mReactContext = null;
|
||||
|
||||
public AsyncStorageHelper(Context mReactContext) {
|
||||
this.mReactContext = mReactContext;
|
||||
}
|
||||
|
||||
public HashMap<String, String> multiGet(ReadableArray keys) {
|
||||
HashMap<String, String> results = new HashMap<>(keys.size());
|
||||
|
||||
HashSet<String> keysRemaining = new HashSet<>();
|
||||
String[] columns = {KEY_COLUMN, VALUE_COLUMN};
|
||||
ReactDatabaseSupplier reactDatabaseSupplier = ReactDatabaseSupplier.getInstance(this.mReactContext);
|
||||
for (int keyStart = 0; keyStart < keys.size(); keyStart += MAX_SQL_KEYS) {
|
||||
int keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS);
|
||||
Cursor cursor = reactDatabaseSupplier.get().query(
|
||||
TABLE_CATALYST,
|
||||
columns,
|
||||
buildKeySelection(keyCount),
|
||||
buildKeySelectionArgs(keys, keyStart, keyCount),
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
keysRemaining.clear();
|
||||
|
||||
try {
|
||||
if (cursor.getCount() != keys.size()) {
|
||||
// some keys have not been found - insert them with null into the final array
|
||||
for (int keyIndex = keyStart; keyIndex < keyStart + keyCount; keyIndex++) {
|
||||
keysRemaining.add(keys.getString(keyIndex));
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
results.put(cursor.getString(0), cursor.getString(1));
|
||||
keysRemaining.remove(cursor.getString(0));
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return new HashMap<>(1);
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
for (String key : keysRemaining) {
|
||||
results.put(key, null);
|
||||
}
|
||||
keysRemaining.clear();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static String buildKeySelection(int selectionCount) {
|
||||
String[] list = new String[selectionCount];
|
||||
Arrays.fill(list, "?");
|
||||
return KEY_COLUMN + " IN (" + TextUtils.join(", ", list) + ")";
|
||||
}
|
||||
|
||||
private static String[] buildKeySelectionArgs(ReadableArray keys, int start, int count) {
|
||||
String[] selectionArgs = new String[count];
|
||||
for (int keyIndex = 0; keyIndex < count; keyIndex++) {
|
||||
selectionArgs[keyIndex] = keys.getString(start + keyIndex);
|
||||
}
|
||||
return selectionArgs;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.mattermost.rnbeta.react_native_interface;
|
||||
|
||||
import com.facebook.react.bridge.Dynamic;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReadableType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* KeysReadableArray: Helper class that abstracts boilerplate
|
||||
*/
|
||||
public class KeysReadableArray implements ReadableArray {
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNull(int index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(int index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(int index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableArray getArray(int index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableMap getMap(int index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dynamic getDynamic(int index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableType getType(int index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Object> toArrayList() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.mattermost.rnbeta.react_native_interface;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
|
||||
/**
|
||||
* ResolvePromise: Helper class that abstracts boilerplate
|
||||
*/
|
||||
public class ResolvePromise implements Promise {
|
||||
@Override
|
||||
public void resolve(@javax.annotation.Nullable Object value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String code, String message) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String code, Throwable e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String code, String message, Throwable e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String message) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(Throwable reason) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,20 @@
|
|||
package com.mattermost.share;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.ReactActivity;
|
||||
import com.mattermost.rnbeta.MainApplication;
|
||||
|
||||
public class ShareActivity extends ReactActivity {
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
return "MattermostShare";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
MainApplication app = (MainApplication) this.getApplication();
|
||||
app.sharedExtensionIsOpened = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.mattermost.share;
|
|||
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
|
@ -10,23 +9,25 @@ import com.facebook.react.bridge.ReactMethod;
|
|||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.mattermost.rnbeta.MainApplication;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import java.io.InputStream;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
|
|
@ -37,8 +38,11 @@ import okhttp3.Response;
|
|||
public class ShareModule extends ReactContextBaseJavaModule {
|
||||
private final OkHttpClient client = new OkHttpClient();
|
||||
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
public ShareModule(ReactApplicationContext reactContext) {
|
||||
private final MainApplication mApplication;
|
||||
|
||||
public ShareModule(MainApplication application, ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mApplication = application;
|
||||
}
|
||||
private File tempFolder;
|
||||
|
||||
|
|
@ -59,6 +63,15 @@ public class ShareModule extends ReactContextBaseJavaModule {
|
|||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
HashMap<String, Object> constants = new HashMap<>(1);
|
||||
constants.put("isOpened", mApplication.sharedExtensionIsOpened);
|
||||
mApplication.sharedExtensionIsOpened = false;
|
||||
return constants;
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void close(ReadableMap data) {
|
||||
this.clear();
|
||||
|
|
|
|||
|
|
@ -5,15 +5,22 @@ import com.facebook.react.bridge.JavaScriptModule;
|
|||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.mattermost.rnbeta.MainApplication;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SharePackage implements ReactPackage {
|
||||
MainApplication mApplication;
|
||||
|
||||
public SharePackage(MainApplication application) {
|
||||
mApplication = application;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
return Arrays.<NativeModule>asList(new ShareModule(reactContext));
|
||||
return Arrays.<NativeModule>asList(new ShareModule(mApplication, reactContext));
|
||||
}
|
||||
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
rootProject.name = 'Mattermost'
|
||||
include ':react-native-document-picker'
|
||||
project(':react-native-document-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-document-picker/android')
|
||||
include ':react-native-keychain'
|
||||
project(':react-native-keychain').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keychain/android')
|
||||
include ':react-native-doc-viewer'
|
||||
project(':react-native-doc-viewer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-doc-viewer/android')
|
||||
include ':react-native-video'
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {getClientConfig, getDataRetentionPolicy, getLicenseConfig} from 'matterm
|
|||
import {getPosts} from 'mattermost-redux/actions/posts';
|
||||
import {getMyTeams, getMyTeamMembers, selectTeam} from 'mattermost-redux/actions/teams';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {recordTime} from 'app/utils/segment';
|
||||
|
||||
import {
|
||||
|
|
@ -17,6 +18,15 @@ import {
|
|||
retryGetPostsAction,
|
||||
} from 'app/actions/views/channel';
|
||||
|
||||
export function startDataCleanup() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ViewTypes.DATA_CLEANUP,
|
||||
payload: getState(),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function loadConfigAndLicense() {
|
||||
return async (dispatch, getState) => {
|
||||
const {currentUserId} = getState().entities.users;
|
||||
|
|
|
|||
290
app/app.js
Normal file
290
app/app.js
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
/* eslint-disable global-require*/
|
||||
import {AsyncStorage, NativeModules} from 'react-native';
|
||||
import {setGenericPassword, getGenericPassword, resetGenericPassword} from 'react-native-keychain';
|
||||
|
||||
import {loadMe} from 'mattermost-redux/actions/users';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import tracker from 'app/utils/time_tracker';
|
||||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
|
||||
import {getTranslations as getLocalTranslations} from 'app/i18n';
|
||||
import {store, handleManagedConfig} from 'app/mattermost';
|
||||
import avoidNativeBridge from 'app/utils/avoid_native_bridge';
|
||||
|
||||
const {Initialization} = NativeModules;
|
||||
|
||||
const DEVICE_SECURE_CACHE_KEY = 'DEVICE_SECURE_CACHE_KEY';
|
||||
const TOOLBAR_BACKGROUND = 'TOOLBAR_BACKGROUND';
|
||||
const TOOLBAR_TEXT_COLOR = 'TOOLBAR_TEXT_COLOR';
|
||||
const APP_BACKGROUND = 'APP_BACKGROUND';
|
||||
|
||||
export default class App {
|
||||
constructor() {
|
||||
// Usage: app.js
|
||||
this.shouldRelaunchWhenActive = false;
|
||||
this.inBackgroundSince = null;
|
||||
|
||||
// Usage: screen/entry.js
|
||||
this.startAppFromPushNotification = false;
|
||||
this.isNotificationsConfigured = false;
|
||||
this.allowOtherServers = true;
|
||||
this.appStarted = false;
|
||||
this.emmEnabled = false;
|
||||
this.translations = null;
|
||||
this.toolbarBackground = null;
|
||||
this.toolbarTextColor = null;
|
||||
this.appBackground = null;
|
||||
|
||||
// Usage utils/push_notifications.js
|
||||
this.replyNotificationData = null;
|
||||
this.deviceToken = null;
|
||||
|
||||
// Usage credentials
|
||||
this.currentUserId = null;
|
||||
this.token = null;
|
||||
this.url = null;
|
||||
|
||||
this.getStartupThemes();
|
||||
this.getAppCredentials();
|
||||
}
|
||||
|
||||
getTranslations = () => {
|
||||
if (this.translations) {
|
||||
return this.translations;
|
||||
}
|
||||
|
||||
const state = store.getState();
|
||||
const locale = getCurrentLocale(state);
|
||||
|
||||
this.translations = getLocalTranslations(locale);
|
||||
return this.translations;
|
||||
};
|
||||
|
||||
getManagedConfig = async () => {
|
||||
try {
|
||||
const shouldStart = await avoidNativeBridge(
|
||||
() => {
|
||||
return Initialization.managedConfigResult;
|
||||
},
|
||||
() => {
|
||||
return Initialization.managedConfigResult;
|
||||
},
|
||||
() => {
|
||||
return AsyncStorage.getItem(DEVICE_SECURE_CACHE_KEY);
|
||||
}
|
||||
);
|
||||
if (shouldStart !== null) {
|
||||
return shouldStart === 'true';
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
getAppCredentials = async () => {
|
||||
try {
|
||||
const credentials = await avoidNativeBridge(
|
||||
() => {
|
||||
return Initialization.credentialsExist;
|
||||
},
|
||||
() => {
|
||||
return Initialization.credentials;
|
||||
},
|
||||
() => {
|
||||
return getGenericPassword();
|
||||
}
|
||||
);
|
||||
|
||||
if (credentials) {
|
||||
const usernameParsed = credentials.username.split(',');
|
||||
const passwordParsed = credentials.password.split(',');
|
||||
|
||||
// username == deviceToken, currentUserId
|
||||
// password == token, url
|
||||
if (usernameParsed.length === 2 && passwordParsed.length === 2) {
|
||||
const [deviceToken, currentUserId] = usernameParsed;
|
||||
const [token, url] = passwordParsed;
|
||||
|
||||
this.deviceToken = deviceToken;
|
||||
this.currentUserId = currentUserId;
|
||||
this.token = token;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
getStartupThemes = async () => {
|
||||
try {
|
||||
const [
|
||||
toolbarBackground,
|
||||
toolbarTextColor,
|
||||
appBackground,
|
||||
] = await avoidNativeBridge(
|
||||
() => {
|
||||
return Initialization.themesExist;
|
||||
},
|
||||
() => {
|
||||
return [
|
||||
Initialization.toolbarBackground,
|
||||
Initialization.toolbarTextColor,
|
||||
Initialization.appBackground,
|
||||
];
|
||||
},
|
||||
() => {
|
||||
return Promise.all([
|
||||
AsyncStorage.getItem(TOOLBAR_BACKGROUND),
|
||||
AsyncStorage.getItem(TOOLBAR_TEXT_COLOR),
|
||||
AsyncStorage.getItem(APP_BACKGROUND),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
if (toolbarBackground) {
|
||||
this.toolbarBackground = toolbarBackground;
|
||||
this.toolbarTextColor = toolbarTextColor;
|
||||
this.appBackground = appBackground;
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
setManagedConfig = (shouldStart) => {
|
||||
AsyncStorage.setItem(DEVICE_SECURE_CACHE_KEY, shouldStart.toString());
|
||||
};
|
||||
|
||||
setAppCredentials = (deviceToken, currentUserId, token, url) => {
|
||||
if (!currentUserId) {
|
||||
return;
|
||||
}
|
||||
const username = `${deviceToken}, ${currentUserId}`;
|
||||
const password = `${token},${url}`;
|
||||
setGenericPassword(username, password);
|
||||
};
|
||||
|
||||
setStartupThemes = (toolbarBackground, toolbarTextColor, appBackground) => {
|
||||
AsyncStorage.setItem(TOOLBAR_BACKGROUND, toolbarBackground);
|
||||
AsyncStorage.setItem(TOOLBAR_TEXT_COLOR, toolbarTextColor);
|
||||
AsyncStorage.setItem(APP_BACKGROUND, appBackground);
|
||||
};
|
||||
|
||||
setStartAppFromPushNotification = (startAppFromPushNotification) => {
|
||||
this.startAppFromPushNotification = startAppFromPushNotification;
|
||||
};
|
||||
|
||||
setIsNotificationsConfigured = (isNotificationsConfigured) => {
|
||||
this.isNotificationsConfigured = isNotificationsConfigured;
|
||||
};
|
||||
|
||||
setAllowOtherServers = (allowOtherServers) => {
|
||||
this.allowOtherServers = allowOtherServers;
|
||||
};
|
||||
|
||||
setAppStarted = (appStarted) => {
|
||||
this.appStarted = appStarted;
|
||||
};
|
||||
|
||||
setEMMEnabled = (emmEnabled) => {
|
||||
this.emmEnabled = emmEnabled;
|
||||
};
|
||||
|
||||
setDeviceToken = (deviceToken) => {
|
||||
this.deviceToken = deviceToken;
|
||||
};
|
||||
|
||||
setReplyNotificationData = (replyNotificationData) => {
|
||||
this.replyNotificationData = replyNotificationData;
|
||||
};
|
||||
|
||||
setInBackgroundSince = (inBackgroundSince) => {
|
||||
this.inBackgroundSince = inBackgroundSince;
|
||||
};
|
||||
|
||||
setShouldRelaunchWhenActive = (shouldRelaunchWhenActive) => {
|
||||
this.shouldRelaunchWhenActive = shouldRelaunchWhenActive;
|
||||
};
|
||||
|
||||
clearNativeCache = () => {
|
||||
resetGenericPassword();
|
||||
AsyncStorage.multiRemove([
|
||||
DEVICE_SECURE_CACHE_KEY,
|
||||
TOOLBAR_BACKGROUND,
|
||||
TOOLBAR_TEXT_COLOR,
|
||||
APP_BACKGROUND,
|
||||
]);
|
||||
};
|
||||
|
||||
verifyManagedConfigCache = async (shouldStartCache) => {
|
||||
// since we are caching managed device results
|
||||
// we should verify after using the cache
|
||||
const shouldStart = await handleManagedConfig();
|
||||
if (shouldStartCache && !shouldStart) {
|
||||
this.setManagedConfig(false);
|
||||
mattermostManaged.quitApp();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setManagedConfig(true);
|
||||
};
|
||||
|
||||
launchApp = async () => {
|
||||
const shouldStartCache = await this.getManagedConfig();
|
||||
if (shouldStartCache) {
|
||||
this.startApp();
|
||||
this.verifyManagedConfigCache(shouldStartCache);
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldStart = await handleManagedConfig();
|
||||
if (shouldStart) {
|
||||
this.setManagedConfig(shouldStart);
|
||||
this.startApp();
|
||||
}
|
||||
};
|
||||
|
||||
startApp = () => {
|
||||
if (this.appStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {dispatch, getState} = store;
|
||||
const {entities} = getState();
|
||||
|
||||
let screen = 'SelectServer';
|
||||
if (entities) {
|
||||
const {credentials} = entities.general;
|
||||
|
||||
if (credentials.token && credentials.url) {
|
||||
screen = 'Channel';
|
||||
tracker.initialLoad = Date.now();
|
||||
loadMe()(dispatch, getState);
|
||||
}
|
||||
}
|
||||
|
||||
switch (screen) {
|
||||
case 'SelectServer':
|
||||
EventEmitter.emit(ViewTypes.LAUNCH_LOGIN, true);
|
||||
break;
|
||||
case 'Channel':
|
||||
EventEmitter.emit(ViewTypes.LAUNCH_CHANNEL, true);
|
||||
break;
|
||||
}
|
||||
|
||||
this.setStartAppFromPushNotification(false);
|
||||
this.setAppStarted(true);
|
||||
}
|
||||
}
|
||||
96
app/components/start/empty_toolbar.js
Normal file
96
app/components/start/empty_toolbar.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Platform, View} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
const {
|
||||
ANDROID_TOP_LANDSCAPE,
|
||||
ANDROID_TOP_PORTRAIT,
|
||||
IOS_TOP_LANDSCAPE,
|
||||
IOS_TOP_PORTRAIT,
|
||||
STATUS_BAR_HEIGHT,
|
||||
} = ViewTypes;
|
||||
|
||||
export default class EmptyToolbar extends PureComponent {
|
||||
static propTypes = {
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.isX = DeviceInfo.getModel() === 'iPhone X';
|
||||
}
|
||||
|
||||
render() {
|
||||
const {isLandscape, theme} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
const padding = {paddingHorizontal: 0};
|
||||
|
||||
let height;
|
||||
switch (Platform.OS) {
|
||||
case 'android':
|
||||
height = ANDROID_TOP_PORTRAIT;
|
||||
if (isLandscape) {
|
||||
height = ANDROID_TOP_LANDSCAPE;
|
||||
}
|
||||
break;
|
||||
case 'ios':
|
||||
height = IOS_TOP_PORTRAIT - STATUS_BAR_HEIGHT;
|
||||
if (isLandscape) {
|
||||
height = IOS_TOP_LANDSCAPE;
|
||||
}
|
||||
|
||||
if (this.isX && isLandscape) {
|
||||
padding.paddingHorizontal = 10;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[style.header, padding, {height}]}>
|
||||
<View style={style.button_container}>
|
||||
<View style={style.button_wrapper}>
|
||||
<Icon
|
||||
name='md-menu'
|
||||
size={25}
|
||||
color={theme.sidebarHeaderTextColor}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
header: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
width: '100%',
|
||||
zIndex: 10,
|
||||
},
|
||||
button_container: {
|
||||
width: 55,
|
||||
},
|
||||
button_wrapper: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -12,11 +12,15 @@ import tinyColor from 'tinycolor2';
|
|||
export default class StatusBar extends PureComponent {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
headerColor: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const headerColor = tinyColor(theme.sidebarHeaderBg);
|
||||
let headerColor = tinyColor(theme.sidebarHeaderBg);
|
||||
if (this.props.headerColor) {
|
||||
headerColor = tinyColor(this.props.headerColor);
|
||||
}
|
||||
let barStyle = 'light-content';
|
||||
if (headerColor.isLight() && Platform.OS === 'ios') {
|
||||
barStyle = 'dark-content';
|
||||
|
|
|
|||
|
|
@ -64,6 +64,9 @@ const ViewTypes = keyMirror({
|
|||
ANNOUNCEMENT_BANNER: null,
|
||||
|
||||
INCREMENT_EMOJI_PICKER_PAGE: null,
|
||||
|
||||
LAUNCH_LOGIN: null,
|
||||
LAUNCH_CHANNEL: null,
|
||||
});
|
||||
|
||||
export default {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -35,7 +35,7 @@ class PushNotification {
|
|||
const notification = new Notification(deviceNotification);
|
||||
const data = notification.getData();
|
||||
|
||||
if (this.onReply && AppState.currentState === 'background') {
|
||||
if (this.onReply) {
|
||||
this.onReply(data, data.text, parseInt(data.badge, 10) - parseInt(data.msg_count, 10));
|
||||
} else {
|
||||
this.deviceNotification = {
|
||||
|
|
|
|||
274
app/screens/entry/entry.js
Normal file
274
app/screens/entry/entry.js
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
/* eslint-disable global-require*/
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View,
|
||||
AppState,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {
|
||||
app,
|
||||
store,
|
||||
} from 'app/mattermost';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import {stripTrailingSlashes} from 'app/utils/url';
|
||||
|
||||
import EmptyToolbar from 'app/components/start/empty_toolbar';
|
||||
import Loading from 'app/components/loading';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
|
||||
const lazyLoadSelectServer = () => {
|
||||
return require('app/screens/select_server').default;
|
||||
};
|
||||
|
||||
const lazyLoadChannel = () => {
|
||||
return require('app/screens/channel').default;
|
||||
};
|
||||
|
||||
const lazyLoadPushNotifications = () => {
|
||||
return require('app/utils/push_notifications').configurePushNotifications;
|
||||
};
|
||||
|
||||
const lazyLoadReplyPushNotifications = () => {
|
||||
return require('app/utils/push_notifications').onPushNotificationReply;
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry Component:
|
||||
* With very little overhead navigate to
|
||||
* - Login or
|
||||
* - Channel Component
|
||||
*
|
||||
* The idea is to render something to the user as soon as possible
|
||||
*/
|
||||
export default class Entry extends PureComponent {
|
||||
static propTypes = {
|
||||
config: PropTypes.object,
|
||||
theme: PropTypes.object,
|
||||
navigator: PropTypes.object,
|
||||
isLandscape: PropTypes.bool,
|
||||
hydrationComplete: PropTypes.bool,
|
||||
initializeModules: PropTypes.func.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
setDeviceToken: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
launchLogin: false,
|
||||
launchChannel: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
Client4.setUserAgent(DeviceInfo.getUserAgent());
|
||||
this.unsubscribeFromStore = store.subscribe(this.listenForHydration);
|
||||
|
||||
EventEmitter.on(ViewTypes.LAUNCH_LOGIN, this.handleLaunchLogin);
|
||||
EventEmitter.on(ViewTypes.LAUNCH_CHANNEL, this.handleLaunchChannel);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off(ViewTypes.LAUNCH_LOGIN, this.handleLaunchLogin);
|
||||
EventEmitter.off(ViewTypes.LAUNCH_CHANNEL, this.handleLaunchChannel);
|
||||
}
|
||||
|
||||
handleLaunchLogin = (initializeModules) => {
|
||||
this.setState({launchLogin: true});
|
||||
|
||||
if (initializeModules) {
|
||||
this.props.initializeModules();
|
||||
}
|
||||
};
|
||||
|
||||
handleLaunchChannel = (initializeModules) => {
|
||||
this.setState({launchChannel: true});
|
||||
|
||||
if (initializeModules) {
|
||||
this.props.initializeModules();
|
||||
}
|
||||
};
|
||||
|
||||
listenForHydration = () => {
|
||||
const {getState} = store;
|
||||
const state = getState();
|
||||
|
||||
if (!app.isNotificationsConfigured) {
|
||||
this.configurePushNotifications();
|
||||
}
|
||||
|
||||
if (state.views.root.hydrationComplete) {
|
||||
this.unsubscribeFromStore();
|
||||
|
||||
this.setAppCredentials();
|
||||
this.setStartupThemes();
|
||||
this.setReplyNotifications();
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
this.launchForAndroid();
|
||||
return;
|
||||
}
|
||||
|
||||
this.launchForiOS();
|
||||
}
|
||||
};
|
||||
|
||||
configurePushNotifications = () => {
|
||||
const configureNotifications = lazyLoadPushNotifications();
|
||||
configureNotifications();
|
||||
};
|
||||
|
||||
setAppCredentials = () => {
|
||||
const {
|
||||
actions: {
|
||||
setDeviceToken,
|
||||
},
|
||||
} = this.props;
|
||||
const {getState} = store;
|
||||
const state = getState();
|
||||
|
||||
const {credentials} = state.entities.general;
|
||||
const {currentUserId} = state.entities.users;
|
||||
|
||||
if (app.deviceToken) {
|
||||
setDeviceToken(app.deviceToken);
|
||||
}
|
||||
|
||||
if (credentials.token && credentials.url) {
|
||||
Client4.setToken(credentials.token);
|
||||
Client4.setUrl(stripTrailingSlashes(credentials.url));
|
||||
}
|
||||
|
||||
if (currentUserId) {
|
||||
Client4.setUserId(currentUserId);
|
||||
}
|
||||
|
||||
app.setAppCredentials(app.deviceToken, currentUserId, credentials.token, credentials.url);
|
||||
};
|
||||
|
||||
setStartupThemes = () => {
|
||||
const {theme} = this.props;
|
||||
if (app.toolbarBackground === theme.sidebarHeaderBg) {
|
||||
return;
|
||||
}
|
||||
|
||||
app.setStartupThemes(
|
||||
theme.sidebarHeaderBg,
|
||||
theme.sidebarHeaderTextColor,
|
||||
theme.centerChannelBg
|
||||
);
|
||||
};
|
||||
|
||||
setReplyNotifications = () => {
|
||||
const notification = PushNotifications.getNotification();
|
||||
|
||||
// If notification exists, it means that the app was started through a reply
|
||||
// and the app was not sitting in the background nor opened
|
||||
if (notification || app.replyNotificationData) {
|
||||
const onPushNotificationReply = lazyLoadReplyPushNotifications();
|
||||
const notificationData = notification || app.replyNotificationData;
|
||||
const {data, text, badge, completed} = notificationData;
|
||||
|
||||
onPushNotificationReply(data, text, badge, completed);
|
||||
PushNotifications.resetNotification();
|
||||
}
|
||||
};
|
||||
|
||||
launchForAndroid = () => {
|
||||
if (app.startAppFromPushNotification) {
|
||||
return;
|
||||
}
|
||||
|
||||
app.launchApp();
|
||||
};
|
||||
|
||||
launchForiOS = () => {
|
||||
const appNotActive = AppState.currentState !== 'active';
|
||||
|
||||
if (appNotActive) {
|
||||
// for iOS replying from push notification starts the app in the background
|
||||
app.setShouldRelaunchWhenActive(true);
|
||||
} else {
|
||||
app.launchApp();
|
||||
}
|
||||
};
|
||||
|
||||
renderLogin = () => {
|
||||
const SelectServer = lazyLoadSelectServer();
|
||||
return (
|
||||
<SelectServer
|
||||
navigator={this.props.navigator}
|
||||
allowOtherServers={app.allowOtherServers}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderChannel = () => {
|
||||
const ChannelScreen = lazyLoadChannel();
|
||||
|
||||
return (
|
||||
<ChannelScreen
|
||||
navigator={this.props.navigator}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
navigator,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
|
||||
if (this.state.launchLogin) {
|
||||
return this.renderLogin();
|
||||
}
|
||||
|
||||
if (this.state.launchChannel) {
|
||||
return this.renderChannel();
|
||||
}
|
||||
|
||||
let toolbar = null;
|
||||
const backgroundColor = app.appBackground ? app.appBackground : '#ffff';
|
||||
if (app.token && app.toolbarBackground) {
|
||||
const toolbarTheme = {
|
||||
sidebarHeaderBg: app.toolbarBackground,
|
||||
sidebarHeaderTextColor: app.toolbarTextColor,
|
||||
};
|
||||
|
||||
toolbar = (
|
||||
<View>
|
||||
<StatusBar headerColor={app.toolbarBackground}/>
|
||||
<EmptyToolbar
|
||||
theme={toolbarTheme}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
navBarBackgroundColor={app.toolbarBackground}
|
||||
backgroundColor={backgroundColor}
|
||||
navigator={navigator}
|
||||
>
|
||||
{toolbar}
|
||||
<Loading/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
36
app/screens/entry/index.js
Normal file
36
app/screens/entry/index.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
/* eslint-disable global-require*/
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {setDeviceToken} from 'mattermost-redux/actions/general';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
const lazyLoadEntry = () => {
|
||||
return require('./entry').default;
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {config} = state.entities.general;
|
||||
|
||||
return {
|
||||
config,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
hydrationComplete: state.views.root.hydrationComplete,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
setDeviceToken,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(lazyLoadEntry());
|
||||
|
|
@ -4,52 +4,6 @@
|
|||
import React from 'react';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import About from 'app/screens/about';
|
||||
|
||||
import AddReaction from 'app/screens/add_reaction';
|
||||
import AdvancedSettings from 'app/screens/settings/advanced_settings';
|
||||
import Channel from 'app/screens/channel';
|
||||
import ChannelAddMembers from 'app/screens/channel_add_members';
|
||||
import ChannelInfo from 'app/screens/channel_info';
|
||||
import ChannelMembers from 'app/screens/channel_members';
|
||||
import ChannelPeek from 'app/screens/channel_peek';
|
||||
import ClientUpgrade from 'app/screens/client_upgrade';
|
||||
import ClockDisplay from 'app/screens/clock_display';
|
||||
import Code from 'app/screens/code';
|
||||
import CreateChannel from 'app/screens/create_channel';
|
||||
import DisplaySettings from 'app/screens/settings/display_settings';
|
||||
import EditChannel from 'app/screens/edit_channel';
|
||||
import EditPost from 'app/screens/edit_post';
|
||||
import EditProfile from 'app/screens/edit_profile';
|
||||
import FlaggedPosts from 'app/screens/flagged_posts';
|
||||
import ImagePreview from 'app/screens/image_preview';
|
||||
import TextPreview from 'app/screens/text_preview';
|
||||
import Login from 'app/screens/login';
|
||||
import LoginOptions from 'app/screens/login_options';
|
||||
import LongPost from 'app/screens/long_post';
|
||||
import Mfa from 'app/screens/mfa';
|
||||
import MoreChannels from 'app/screens/more_channels';
|
||||
import MoreDirectMessages from 'app/screens/more_dms';
|
||||
import Notification from 'app/screens/notification';
|
||||
import NotificationSettings from 'app/screens/settings/notification_settings';
|
||||
import NotificationSettingsEmail from 'app/screens/settings/notification_settings_email';
|
||||
import NotificationSettingsMentions from 'app/screens/settings/notification_settings_mentions';
|
||||
import NotificationSettingsMentionsKeywords from 'app/screens/settings/notification_settings_mentions_keywords';
|
||||
import NotificationSettingsMobile from 'app/screens/settings/notification_settings_mobile';
|
||||
import OptionsModal from 'app/screens/options_modal';
|
||||
import Permalink from 'app/screens/permalink';
|
||||
import RecentMentions from 'app/screens/recent_mentions';
|
||||
import Root from 'app/screens/root';
|
||||
import SSO from 'app/screens/sso';
|
||||
import Search from 'app/screens/search';
|
||||
import SelectServer from 'app/screens/select_server';
|
||||
import SelectTeam from 'app/screens/select_team';
|
||||
import Settings from 'app/screens/settings/general';
|
||||
import Table from 'app/screens/table';
|
||||
import TableImage from 'app/screens/table_image';
|
||||
import Thread from 'app/screens/thread';
|
||||
import UserProfile from 'app/screens/user_profile';
|
||||
|
||||
import IntlWrapper from 'app/components/root';
|
||||
|
||||
function wrapWithContextProvider(Comp, excludeEvents = true) {
|
||||
|
|
@ -67,48 +21,49 @@ function wrapWithContextProvider(Comp, excludeEvents = true) {
|
|||
}
|
||||
|
||||
export function registerScreens(store, Provider) {
|
||||
Navigation.registerComponent('About', () => wrapWithContextProvider(About), store, Provider);
|
||||
Navigation.registerComponent('AddReaction', () => wrapWithContextProvider(AddReaction), store, Provider);
|
||||
Navigation.registerComponent('AdvancedSettings', () => wrapWithContextProvider(AdvancedSettings), store, Provider);
|
||||
Navigation.registerComponent('Channel', () => wrapWithContextProvider(Channel, false), store, Provider);
|
||||
Navigation.registerComponent('ChannelAddMembers', () => wrapWithContextProvider(ChannelAddMembers), store, Provider);
|
||||
Navigation.registerComponent('ChannelInfo', () => wrapWithContextProvider(ChannelInfo), store, Provider);
|
||||
Navigation.registerComponent('ChannelMembers', () => wrapWithContextProvider(ChannelMembers), store, Provider);
|
||||
Navigation.registerComponent('ChannelPeek', () => wrapWithContextProvider(ChannelPeek), store, Provider);
|
||||
Navigation.registerComponent('ClientUpgrade', () => wrapWithContextProvider(ClientUpgrade), store, Provider);
|
||||
Navigation.registerComponent('ClockDisplay', () => wrapWithContextProvider(ClockDisplay), store, Provider);
|
||||
Navigation.registerComponent('Code', () => wrapWithContextProvider(Code), store, Provider);
|
||||
Navigation.registerComponent('CreateChannel', () => wrapWithContextProvider(CreateChannel), store, Provider);
|
||||
Navigation.registerComponent('DisplaySettings', () => wrapWithContextProvider(DisplaySettings), store, Provider);
|
||||
Navigation.registerComponent('EditChannel', () => wrapWithContextProvider(EditChannel), store, Provider);
|
||||
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(EditPost), store, Provider);
|
||||
Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(EditProfile), store, Provider);
|
||||
Navigation.registerComponent('FlaggedPosts', () => wrapWithContextProvider(FlaggedPosts), store, Provider);
|
||||
Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(ImagePreview), store, Provider);
|
||||
Navigation.registerComponent('TextPreview', () => wrapWithContextProvider(TextPreview), store, Provider);
|
||||
Navigation.registerComponent('Login', () => wrapWithContextProvider(Login), store, Provider);
|
||||
Navigation.registerComponent('LoginOptions', () => wrapWithContextProvider(LoginOptions), store, Provider);
|
||||
Navigation.registerComponent('LongPost', () => wrapWithContextProvider(LongPost), store, Provider);
|
||||
Navigation.registerComponent('MFA', () => wrapWithContextProvider(Mfa), store, Provider);
|
||||
Navigation.registerComponent('MoreChannels', () => wrapWithContextProvider(MoreChannels), store, Provider);
|
||||
Navigation.registerComponent('MoreDirectMessages', () => wrapWithContextProvider(MoreDirectMessages), store, Provider);
|
||||
Navigation.registerComponent('Notification', () => wrapWithContextProvider(Notification), store, Provider);
|
||||
Navigation.registerComponent('NotificationSettings', () => wrapWithContextProvider(NotificationSettings), store, Provider);
|
||||
Navigation.registerComponent('NotificationSettingsEmail', () => wrapWithContextProvider(NotificationSettingsEmail), store, Provider);
|
||||
Navigation.registerComponent('NotificationSettingsMentions', () => wrapWithContextProvider(NotificationSettingsMentions), store, Provider);
|
||||
Navigation.registerComponent('NotificationSettingsMentionsKeywords', () => wrapWithContextProvider(NotificationSettingsMentionsKeywords), store, Provider);
|
||||
Navigation.registerComponent('NotificationSettingsMobile', () => wrapWithContextProvider(NotificationSettingsMobile), store, Provider);
|
||||
Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(OptionsModal), store, Provider);
|
||||
Navigation.registerComponent('Permalink', () => wrapWithContextProvider(Permalink), store, Provider);
|
||||
Navigation.registerComponent('RecentMentions', () => wrapWithContextProvider(RecentMentions), store, Provider);
|
||||
Navigation.registerComponent('Root', () => Root, store, Provider);
|
||||
Navigation.registerComponent('Search', () => wrapWithContextProvider(Search), store, Provider);
|
||||
Navigation.registerComponent('SelectServer', () => wrapWithContextProvider(SelectServer), store, Provider);
|
||||
Navigation.registerComponent('SelectTeam', () => wrapWithContextProvider(SelectTeam), store, Provider);
|
||||
Navigation.registerComponent('Settings', () => wrapWithContextProvider(Settings), store, Provider);
|
||||
Navigation.registerComponent('SSO', () => wrapWithContextProvider(SSO), store, Provider);
|
||||
Navigation.registerComponent('Table', () => wrapWithContextProvider(Table), store, Provider);
|
||||
Navigation.registerComponent('TableImage', () => wrapWithContextProvider(TableImage), store, Provider);
|
||||
Navigation.registerComponent('Thread', () => wrapWithContextProvider(Thread), store, Provider);
|
||||
Navigation.registerComponent('UserProfile', () => wrapWithContextProvider(UserProfile), store, Provider);
|
||||
Navigation.registerComponent('About', () => wrapWithContextProvider(require('app/screens/about').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('AddReaction', () => wrapWithContextProvider(require('app/screens/add_reaction').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('AdvancedSettings', () => wrapWithContextProvider(require('app/screens/settings/advanced_settings').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('Channel', () => wrapWithContextProvider(require('app/screens/channel').default, false), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('ChannelAddMembers', () => wrapWithContextProvider(require('app/screens/channel_add_members').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('ChannelInfo', () => wrapWithContextProvider(require('app/screens/channel_info').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('ChannelMembers', () => wrapWithContextProvider(require('app/screens/channel_members').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('ChannelPeek', () => wrapWithContextProvider(require('app/screens/channel_peek').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('ClientUpgrade', () => wrapWithContextProvider(require('app/screens/client_upgrade').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('ClockDisplay', () => wrapWithContextProvider(require('app/screens/clock_display').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('Code', () => wrapWithContextProvider(require('app/screens/code').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('CreateChannel', () => wrapWithContextProvider(require('app/screens/create_channel').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('DisplaySettings', () => wrapWithContextProvider(require('app/screens/settings/display_settings').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('EditChannel', () => wrapWithContextProvider(require('app/screens/edit_channel').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('EditPost', () => wrapWithContextProvider(require('app/screens/edit_post').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(require('app/screens/edit_profile').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('Entry', () => wrapWithContextProvider(require('app/screens/entry').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('FlaggedPosts', () => wrapWithContextProvider(require('app/screens/flagged_posts').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('ImagePreview', () => wrapWithContextProvider(require('app/screens/image_preview').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('TextPreview', () => wrapWithContextProvider(require('app/screens/text_preview').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('Login', () => wrapWithContextProvider(require('app/screens/login').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('LoginOptions', () => wrapWithContextProvider(require('app/screens/login_options').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('LongPost', () => wrapWithContextProvider(require('app/screens/long_post').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('MFA', () => wrapWithContextProvider(require('app/screens/mfa').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('MoreChannels', () => wrapWithContextProvider(require('app/screens/more_channels').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('MoreDirectMessages', () => wrapWithContextProvider(require('app/screens/more_dms').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('Notification', () => wrapWithContextProvider(require('app/screens/notification').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('NotificationSettings', () => wrapWithContextProvider(require('app/screens/settings/notification_settings').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('NotificationSettingsEmail', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_email').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('NotificationSettingsMentions', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_mentions').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('NotificationSettingsMentionsKeywords', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_mentions_keywords').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('NotificationSettingsMobile', () => wrapWithContextProvider(require('app/screens/settings/notification_settings_mobile').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('OptionsModal', () => wrapWithContextProvider(require('app/screens/options_modal').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('Permalink', () => wrapWithContextProvider(require('app/screens/permalink').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('RecentMentions', () => wrapWithContextProvider(require('app/screens/recent_mentions').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('Root', () => require('app/screens/root').default, store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('Search', () => wrapWithContextProvider(require('app/screens/search').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('SelectServer', () => wrapWithContextProvider(require('app/screens/select_server').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('SelectTeam', () => wrapWithContextProvider(require('app/screens/select_team').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('Settings', () => wrapWithContextProvider(require('app/screens/settings/general').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('SSO', () => wrapWithContextProvider(require('app/screens/sso').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('Table', () => wrapWithContextProvider(require('app/screens/table').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('TableImage', () => wrapWithContextProvider(require('app/screens/table_image').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('Thread', () => wrapWithContextProvider(require('app/screens/thread').default), store, Provider); //eslint-disable-line global-require
|
||||
Navigation.registerComponent('UserProfile', () => wrapWithContextProvider(require('app/screens/user_profile').default), store, Provider); //eslint-disable-line global-require
|
||||
}
|
||||
|
|
|
|||
19
app/utils/avoid_native_bridge.js
Normal file
19
app/utils/avoid_native_bridge.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
/**
|
||||
* avoidNativeBridge: a helper function that facilitates returning
|
||||
* a constant variable packaged from InitializationModule
|
||||
* or from a NativeModule which will make a call to the native bridge
|
||||
*
|
||||
* Currently only required for Android
|
||||
*/
|
||||
export default function avoidNativeBridge(runOptimized, optimized, fallback) {
|
||||
if (Platform.OS === 'android' && runOptimized()) {
|
||||
return optimized();
|
||||
}
|
||||
|
||||
return fallback();
|
||||
}
|
||||
62
app/utils/error_handling.js
Normal file
62
app/utils/error_handling.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import {
|
||||
Alert,
|
||||
} from 'react-native';
|
||||
|
||||
import {
|
||||
setJSExceptionHandler,
|
||||
setNativeExceptionHandler,
|
||||
} from 'react-native-exception-handler';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {logError} from 'mattermost-redux/actions/errors';
|
||||
import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
|
||||
|
||||
import {purgeOfflineStore} from 'app/actions/views/root';
|
||||
import {
|
||||
captureException,
|
||||
initializeSentry,
|
||||
LOGGER_JAVASCRIPT,
|
||||
LOGGER_NATIVE,
|
||||
} from 'app/utils/sentry';
|
||||
|
||||
import {app, store} from 'app/mattermost';
|
||||
|
||||
const errorHandler = (e, isFatal) => {
|
||||
console.warn('Handling Javascript error ' + JSON.stringify(e)); // eslint-disable-line no-console
|
||||
const {dispatch, getState} = store;
|
||||
|
||||
captureException(e, LOGGER_JAVASCRIPT, store);
|
||||
|
||||
const translations = app.getTranslations();
|
||||
closeWebSocket()(dispatch, getState);
|
||||
|
||||
if (Client4.getUrl()) {
|
||||
logError(e)(dispatch, getState);
|
||||
}
|
||||
|
||||
if (isFatal) {
|
||||
Alert.alert(
|
||||
translations['mobile.error_handler.title'],
|
||||
translations['mobile.error_handler.description'],
|
||||
[{
|
||||
text: translations['mobile.error_handler.button'],
|
||||
onPress: () => {
|
||||
// purge the store
|
||||
purgeOfflineStore()(dispatch, getState);
|
||||
},
|
||||
}],
|
||||
{cancelable: false}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const nativeErrorHandler = (e) => {
|
||||
console.warn('Handling native error ' + JSON.stringify(e)); // eslint-disable-line no-console
|
||||
captureException(e, LOGGER_NATIVE, store);
|
||||
};
|
||||
|
||||
export function initializeErrorHandling() {
|
||||
initializeSentry();
|
||||
setJSExceptionHandler(errorHandler, false);
|
||||
setNativeExceptionHandler(nativeErrorHandler, false);
|
||||
}
|
||||
157
app/utils/push_notifications.js
Normal file
157
app/utils/push_notifications.js
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import {
|
||||
Platform,
|
||||
InteractionManager,
|
||||
} from 'react-native';
|
||||
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
|
||||
import {markChannelAsRead} from 'mattermost-redux/actions/channels';
|
||||
import {setDeviceToken} from 'mattermost-redux/actions/general';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {
|
||||
createPost,
|
||||
loadFromPushNotification,
|
||||
} from 'app/actions/views/root';
|
||||
|
||||
import {stripTrailingSlashes} from 'app/utils/url';
|
||||
|
||||
import {
|
||||
app,
|
||||
store,
|
||||
} from 'app/mattermost';
|
||||
|
||||
const onRegisterDevice = (data) => {
|
||||
app.setIsNotificationsConfigured(true);
|
||||
const state = store.getState();
|
||||
|
||||
let prefix;
|
||||
if (Platform.OS === 'ios') {
|
||||
prefix = General.PUSH_NOTIFY_APPLE_REACT_NATIVE;
|
||||
if (DeviceInfo.getBundleId().includes('rnbeta')) {
|
||||
prefix = `${prefix}beta`;
|
||||
}
|
||||
} else {
|
||||
prefix = General.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
|
||||
}
|
||||
|
||||
const token = `${prefix}:${data.token}`;
|
||||
if (state.views.root.hydrationComplete) {
|
||||
app.setDeviceToken(token);
|
||||
setDeviceToken(token)(store.dispatch, store.getState);
|
||||
} else {
|
||||
app.setDeviceToken(token);
|
||||
}
|
||||
};
|
||||
|
||||
const onPushNotification = (deviceNotification) => {
|
||||
const {dispatch, getState} = store;
|
||||
const state = getState();
|
||||
const {token, url} = state.entities.general.credentials;
|
||||
|
||||
// mark the app as started as soon as possible
|
||||
if (Platform.OS === 'android' && !app.appStarted) {
|
||||
app.setStartAppFromPushNotification(true);
|
||||
}
|
||||
|
||||
const {data, foreground, message, userInfo, userInteraction} = deviceNotification;
|
||||
const notification = {
|
||||
data,
|
||||
message,
|
||||
};
|
||||
|
||||
if (userInfo) {
|
||||
notification.localNotification = userInfo.localNotification;
|
||||
}
|
||||
|
||||
if (data.type === 'clear') {
|
||||
markChannelAsRead(data.channel_id, null, false)(dispatch, getState);
|
||||
} else if (foreground) {
|
||||
EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification);
|
||||
} else if (userInteraction && !notification.localNotification) {
|
||||
EventEmitter.emit('close_channel_drawer');
|
||||
if (app.startAppFromPushNotification) {
|
||||
Client4.setToken(token);
|
||||
Client4.setUrl(stripTrailingSlashes(url));
|
||||
}
|
||||
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
await loadFromPushNotification(notification)(dispatch, getState);
|
||||
|
||||
if (app.startAppFromPushNotification) {
|
||||
app.launchApp();
|
||||
} else {
|
||||
EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const onPushNotificationReply = (data, text, badge, completed) => {
|
||||
const {dispatch, getState} = store;
|
||||
const state = getState();
|
||||
const {currentUserId: reduxCurrentUserId} = state.entities.users;
|
||||
const reduxCredentialsUrl = state.entities.general.credentials.url;
|
||||
const reduxCredentialsToken = state.entities.general.credentials.token;
|
||||
|
||||
const currentUserId = reduxCurrentUserId || app.currentUserId;
|
||||
const url = reduxCredentialsUrl || app.url;
|
||||
const token = reduxCredentialsToken || app.token;
|
||||
|
||||
if (currentUserId) {
|
||||
// one thing to note is that for android it will reply to the last post in the stack
|
||||
const rootId = data.root_id || data.post_id;
|
||||
const post = {
|
||||
user_id: currentUserId,
|
||||
channel_id: data.channel_id,
|
||||
root_id: rootId,
|
||||
parent_id: rootId,
|
||||
message: text,
|
||||
};
|
||||
|
||||
if (!Client4.getUrl()) {
|
||||
// Make sure the Client has the server url set
|
||||
Client4.setUrl(url);
|
||||
}
|
||||
|
||||
if (!Client4.getToken()) {
|
||||
// Make sure the Client has the server token set
|
||||
Client4.setToken(token);
|
||||
}
|
||||
|
||||
createPost(post)(dispatch, getState).then(() => {
|
||||
markChannelAsRead(data.channel_id)(dispatch, getState);
|
||||
|
||||
if (badge >= 0) {
|
||||
PushNotifications.setApplicationIconBadgeNumber(badge);
|
||||
}
|
||||
|
||||
app.setReplyNotificationData(null);
|
||||
}).then(completed);
|
||||
} else {
|
||||
app.setReplyNotificationData({
|
||||
data,
|
||||
text,
|
||||
badge,
|
||||
completed,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const configurePushNotifications = () => {
|
||||
PushNotifications.configure({
|
||||
onRegister: onRegisterDevice,
|
||||
onNotification: onPushNotification,
|
||||
onReply: onPushNotificationReply,
|
||||
popInitialNotification: true,
|
||||
requestPermissions: true,
|
||||
});
|
||||
|
||||
if (app) {
|
||||
app.setIsNotificationsConfigured(true);
|
||||
}
|
||||
};
|
||||
2
index.js
2
index.js
|
|
@ -10,5 +10,3 @@ import ShareExtension from 'share_extension/android';
|
|||
if (Platform.OS === 'android') {
|
||||
AppRegistry.registerComponent('MattermostShare', () => ShareExtension);
|
||||
}
|
||||
|
||||
const app = new Mattermost();
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
|
||||
62A8448264674B4D95A5A7C2 /* OpenSans-Semibold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C78A387124874496AD2C1466 /* OpenSans-Semibold.ttf */; };
|
||||
69AC753E496743BABB7A7124 /* OpenSans-SemiboldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0E617BF0F36D4E738F51D169 /* OpenSans-SemiboldItalic.ttf */; };
|
||||
74D116AD208A8D5600CF8A79 /* libRNKeychain.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 74D116AA208A8D3100CF8A79 /* libRNKeychain.a */; };
|
||||
7F2691A11EE1DC6A007574FE /* libRNNotifications.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F2691A01EE1DC51007574FE /* libRNNotifications.a */; };
|
||||
7F292A711E8AB73400A450A3 /* SplashScreenResource in Resources */ = {isa = PBXBuildFile; fileRef = 7F292A701E8AB73400A450A3 /* SplashScreenResource */; };
|
||||
7F292AA61E8ABB1100A450A3 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7F292AA41E8ABB1100A450A3 /* LaunchScreen.xib */; };
|
||||
|
|
@ -367,6 +368,20 @@
|
|||
remoteGlobalIDString = 2D2A28201D9B03D100D4039D;
|
||||
remoteInfo = "RCTAnimation-tvOS";
|
||||
};
|
||||
74D116A9208A8D3100CF8A79 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 74D116A4208A8D3000CF8A79 /* RNKeychain.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 5D82366F1B0CE05B005A9EF3;
|
||||
remoteInfo = RNKeychain;
|
||||
};
|
||||
74D116AB208A8D3100CF8A79 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 74D116A4208A8D3000CF8A79 /* RNKeychain.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 6478985F1F38BF9100DA1C12;
|
||||
remoteInfo = "RNKeychain-tvOS";
|
||||
};
|
||||
78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */;
|
||||
|
|
@ -691,6 +706,8 @@
|
|||
6561AEAC21CC40B8A72ABB93 /* OpenSans-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Light.ttf"; path = "../assets/fonts/OpenSans-Light.ttf"; sourceTree = "<group>"; };
|
||||
65FD5EA57EBAE06106094B2F /* libPods-Mattermost.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Mattermost.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6BAF8296411D4657B5A0E8F8 /* libRNReactNativeDocViewer.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNReactNativeDocViewer.a; sourceTree = "<group>"; };
|
||||
74D116A3208A8C8400CF8A79 /* libRNKeychain.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libRNKeychain.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
74D116A4208A8D3000CF8A79 /* RNKeychain.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNKeychain.xcodeproj; path = "../node_modules/react-native-keychain/RNKeychain.xcodeproj"; sourceTree = "<group>"; };
|
||||
7535D128F00C4A47A182627E /* libRNCookieManagerIOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCookieManagerIOS.a; sourceTree = "<group>"; };
|
||||
77810F0A063349439B0D8B6B /* libJailMonkey.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libJailMonkey.a; sourceTree = "<group>"; };
|
||||
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
|
||||
|
|
@ -778,6 +795,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74D116AD208A8D5600CF8A79 /* libRNKeychain.a in Frameworks */,
|
||||
37ABD3C81F4CE142001FDE6B /* libART.a in Frameworks */,
|
||||
375218501F4B9EE70035444B /* libRCTCameraRoll.a in Frameworks */,
|
||||
7F43D5E01F6BF994001FC614 /* libRNSVG.a in Frameworks */,
|
||||
|
|
@ -1077,6 +1095,7 @@
|
|||
4B992D7BAAEDF8759DB525B5 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
74D116A3208A8C8400CF8A79 /* libRNKeychain.a */,
|
||||
7FFE32B51FD9CCAA0038C7A0 /* FLAnimatedImage.framework */,
|
||||
7FFE32B61FD9CCAA0038C7A0 /* KSCrash.framework */,
|
||||
7FFE32B71FD9CCAA0038C7A0 /* KSCrash.framework */,
|
||||
|
|
@ -1116,6 +1135,15 @@
|
|||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
74D116A5208A8D3000CF8A79 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
74D116AA208A8D3100CF8A79 /* libRNKeychain.a */,
|
||||
74D116AC208A8D3100CF8A79 /* libRNKeychain.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
78C398B11ACF4ADC00677621 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -1319,6 +1347,7 @@
|
|||
children = (
|
||||
7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */,
|
||||
7F642DE72093530B00F3165E /* RNDeviceInfo.xcodeproj */,
|
||||
74D116A4208A8D3000CF8A79 /* RNKeychain.xcodeproj */,
|
||||
7FD8DE972029ECDE001AAC5E /* RNTableView.xcodeproj */,
|
||||
7F7D7F52201645D300D31155 /* ReactNativePermissions.xcodeproj */,
|
||||
7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */,
|
||||
|
|
@ -1631,6 +1660,10 @@
|
|||
ProductGroup = 7FDB92761F706F45006CDFD1 /* Products */;
|
||||
ProjectRef = 7FDB92751F706F45006CDFD1 /* RNImagePicker.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 74D116A5208A8D3000CF8A79 /* Products */;
|
||||
ProjectRef = 74D116A4208A8D3000CF8A79 /* RNKeychain.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 7F8C49621F3DFC30003A22BA /* Products */;
|
||||
ProjectRef = D0281D64B98143668D6AD42B /* RNLocalAuth.xcodeproj */;
|
||||
|
|
@ -1927,6 +1960,20 @@
|
|||
remoteRef = 5E9157341DD0AC6500FF2AA8 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
74D116AA208A8D3100CF8A79 /* libRNKeychain.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libRNKeychain.a;
|
||||
remoteRef = 74D116A9208A8D3100CF8A79 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
74D116AC208A8D3100CF8A79 /* libRNKeychain.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libRNKeychain.a;
|
||||
remoteRef = 74D116AB208A8D3100CF8A79 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
|
|
|
|||
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -14418,9 +14418,7 @@
|
|||
}
|
||||
},
|
||||
"react-native-device-info": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-0.21.5.tgz",
|
||||
"integrity": "sha512-Bvl7TyVMDbBH3wzvZx0xj3deMhWVRDHdOdjP1MjGVxVVH+bSlCi6oYDScaX2yz5rfi2OvevWWCg8jUqFKeepjA=="
|
||||
"version": "github:enahum/react-native-device-info#27ecfd4a59341bf6ed71d11203d914067070e8df"
|
||||
},
|
||||
"react-native-dismiss-keyboard": {
|
||||
"version": "1.0.0",
|
||||
|
|
@ -14476,7 +14474,7 @@
|
|||
}
|
||||
},
|
||||
"react-native-fetch-blob": {
|
||||
"version": "github:enahum/react-native-fetch-blob#75d5abfa1886665d7eaa947e70b9297b981f0983",
|
||||
"version": "github:enahum/react-native-fetch-blob#2e8f9b05b012496bd3ff9790aace3f2b462e2fee",
|
||||
"requires": {
|
||||
"base-64": "0.1.0",
|
||||
"glob": "7.0.6"
|
||||
|
|
@ -14524,6 +14522,11 @@
|
|||
"react-native-iphone-x-helper": "1.0.2"
|
||||
}
|
||||
},
|
||||
"react-native-keychain": {
|
||||
"version": "3.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-3.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-ijWfHmxTPKnrHtPJiDbKW3D6lRH8O9wbCNEE3xlxEg1WZT+VhP6iiF+HUansNYuxL7Hh7k41GSFfvr3xumfmXA=="
|
||||
},
|
||||
"react-native-linear-gradient": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -25,16 +25,17 @@
|
|||
"react-native-button": "2.3.0",
|
||||
"react-native-circular-progress": "0.2.0",
|
||||
"react-native-cookies": "3.2.0",
|
||||
"react-native-device-info": "0.21.5",
|
||||
"react-native-device-info": "enahum/react-native-device-info.git#27ecfd4a59341bf6ed71d11203d914067070e8df",
|
||||
"react-native-doc-viewer": "2.7.8",
|
||||
"react-native-document-picker": "2.1.0",
|
||||
"react-native-drawer": "2.5.0",
|
||||
"react-native-exception-handler": "2.7.5",
|
||||
"react-native-fast-image": "4.0.8",
|
||||
"react-native-fetch-blob": "enahum/react-native-fetch-blob.git#75d5abfa1886665d7eaa947e70b9297b981f0983",
|
||||
"react-native-fetch-blob": "enahum/react-native-fetch-blob.git#2e8f9b05b012496bd3ff9790aace3f2b462e2fee",
|
||||
"react-native-image-gallery": "enahum/react-native-image-gallery#a98b1051e94f5a394541ca1ff9b15e2c9ffed84f",
|
||||
"react-native-image-picker": "0.26.7",
|
||||
"react-native-keyboard-aware-scroll-view": "0.5.0",
|
||||
"react-native-keychain": "3.0.0-rc.3",
|
||||
"react-native-linear-gradient": "2.4.0",
|
||||
"react-native-local-auth": "enahum/react-native-local-auth.git#cc9ce2f468fbf7b431dfad3191a31aaa9227a6ab",
|
||||
"react-native-navigation": "1.1.450",
|
||||
|
|
|
|||
Loading…
Reference in a new issue