Compare commits

...

8 Commits

Author SHA1 Message Date
Elias Nahum
eea03a93c5 Bump app build number to 247 (#3593) 2019-11-21 13:22:01 -03:00
Mattermost Build
b0516abebf Automated cherry pick of #3590 (#3591)
* Create notification channels only if supported

* Update comment
2019-11-21 13:16:33 -03:00
Elias Nahum
259990ef57 Add react-navigation-stack dependency to fix android build 2019-11-20 22:05:47 -03:00
Elias Nahum
73a2eb478f Fix bad cherry-pick for release-1.25 2019-11-20 21:28:05 -03:00
Elias Nahum
37979d3b7d Bump version to 1.25.1 and Build number to 246 (#3587)
* Bump app build number to 246

* Bump app version number to 1.25.1

* Update Fastlane
2019-11-20 21:21:29 -03:00
Mattermost Build
588d27f0a4 Use LifecycleEventListener for MattermostManagedModule (#3585) 2019-11-20 21:10:27 -03:00
Mattermost Build
82e513647e Avoid OOM crash on iOS13 Share Extension (#3583) 2019-11-20 21:07:12 -03:00
Mattermost Build
931eb91a5a Fixes crash when sso cookies does not contain an expiration date (#3582) 2019-11-20 21:07:04 -03:00
15 changed files with 661 additions and 645 deletions

View File

@@ -123,8 +123,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5"
versionCode 245
versionName "1.25.0"
versionCode 247
versionName "1.25.1"
multiDexEnabled = true
ndk {
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'

View File

@@ -507,6 +507,11 @@ public class CustomPushNotification extends PushNotification {
}
private void createNotificationChannels() {
// Notification channels are not supported in Android Nougat and below
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mHighImportanceChannel = new NotificationChannel("channel_01", "High Importance", NotificationManager.IMPORTANCE_HIGH);

View File

@@ -12,7 +12,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.mattermost.share.ShareModule;
import com.learnium.RNDeviceInfo.RNDeviceModule;
import com.imagepicker.ImagePickerModule;
@@ -209,8 +208,6 @@ public class MainApplication extends NavigationApplication implements INotificat
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);
@@ -270,7 +267,7 @@ public class MainApplication extends NavigationApplication implements INotificat
}
public synchronized Bundle getManagedConfig() {
if (mManagedConfig!= null && mManagedConfig.size() > 0) {
if (mManagedConfig != null && mManagedConfig.size() > 0) {
return mManagedConfig;
}

View File

@@ -1,145 +0,0 @@
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

@@ -4,8 +4,15 @@ import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.os.Bundle;
import android.provider.Settings;
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.NativeModule;
@@ -15,14 +22,34 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.modules.core.DeviceEventManagerModule;
public class MattermostManagedModule extends ReactContextBaseJavaModule {
public class MattermostManagedModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
private static MattermostManagedModule instance;
private boolean shouldBlurAppScreen = false;
private static final String TAG = MattermostManagedModule.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);
handleBlurScreen(managedConfig);
}
}
};
private MattermostManagedModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext.addLifecycleEventListener(this);
}
public static MattermostManagedModule getInstance(ReactApplicationContext reactContext) {
@@ -42,15 +69,6 @@ public class MattermostManagedModule extends ReactContextBaseJavaModule {
return "MattermostManaged";
}
@ReactMethod
public void blurAppScreen(boolean enabled) {
shouldBlurAppScreen = enabled;
}
public boolean isBlurAppScreenEnabled() {
return shouldBlurAppScreen;
}
@ReactMethod
public void getConfig(final Promise promise) {
try {
@@ -96,4 +114,110 @@ public class MattermostManagedModule extends ReactContextBaseJavaModule {
getCurrentActivity().finish();
System.exit(0);
}
@Override
public void onHostResume() {
Activity activity = getCurrentActivity();
Bundle managedConfig = MainApplication.instance.getManagedConfig();
if (activity != null && managedConfig != null) {
activity.registerReceiver(restrictionsReceiver, restrictionsFilter);
}
ReactContext ctx = MainApplication.instance.getRunningReactContext();
Bundle newManagedConfig = null;
if (ctx != null) {
newManagedConfig = MainApplication.instance.loadManagedConfig(ctx);
if (!equalBundles(newManagedConfig, managedConfig)) {
Log.i(TAG, "onResumed Managed Configuration Changed");
sendConfigChanged(newManagedConfig);
}
}
handleBlurScreen(newManagedConfig);
}
@Override
public void onHostPause() {
Activity activity = getCurrentActivity();
Bundle managedConfig = MainApplication.instance.getManagedConfig();
if (activity != null && managedConfig != null) {
try {
activity.unregisterReceiver(restrictionsReceiver);
} catch (IllegalArgumentException e) {
// Just ignore this cause the receiver wasn't registered for this activity
}
}
}
@Override
public void onHostDestroy() {
}
private void handleBlurScreen(Bundle config) {
Activity activity = getCurrentActivity();
boolean blurAppScreen = false;
if (config != null) {
blurAppScreen = Boolean.parseBoolean(config.getString("blurApplicationScreen"));
}
if (blurAppScreen) {
activity.getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
} else {
activity.getWindow().clearFlags(LayoutParams.FLAG_SECURE);
}
}
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 true;
}
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

@@ -5,6 +5,8 @@ import {NativeModules, DeviceEventEmitter} from 'react-native';
import LocalAuth from 'react-native-local-auth';
import JailMonkey from 'jail-monkey';
import {emptyFunction} from 'app/utils/general';
const {MattermostManaged} = NativeModules;
const listeners = [];
@@ -35,7 +37,7 @@ export default {
}
},
authenticate: LocalAuth.auth,
blurAppScreen: MattermostManaged.blurAppScreen,
blurAppScreen: emptyFunction,
appGroupIdentifier: null,
hasSafeAreaInsets: null,
isRunningInSplitView: MattermostManaged.isRunningInSplitView,

View File

@@ -6,17 +6,17 @@ GEM
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
aws-eventstream (1.0.3)
aws-partitions (1.228.0)
aws-sdk-core (3.72.0)
aws-partitions (1.242.0)
aws-sdk-core (3.80.0)
aws-eventstream (~> 1.0, >= 1.0.2)
aws-partitions (~> 1, >= 1.228.0)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.25.0)
aws-sdk-core (~> 3, >= 3.71.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.51.0)
aws-sdk-core (~> 3, >= 3.71.0)
aws-sdk-s3 (1.57.0)
aws-sdk-core (~> 3, >= 3.77.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.1.0)
@@ -43,7 +43,7 @@ GEM
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
fastlane (2.134.0)
fastlane (2.136.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
@@ -94,7 +94,7 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.9)
google-cloud-core (1.4.0)
google-cloud-core (1.4.1)
google-cloud-env (~> 1.0)
google-cloud-env (1.3.0)
faraday (~> 0.11)
@@ -117,7 +117,7 @@ GEM
jmespath (1.4.0)
json (2.2.0)
jwt (2.1.0)
memoist (0.16.0)
memoist (0.16.1)
mime-types (3.3)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009)
@@ -128,7 +128,7 @@ GEM
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
nokogiri (1.10.4)
nokogiri (1.10.5)
mini_portile2 (~> 2.4.0)
os (1.0.1)
plist (3.5.0)

View File

@@ -2852,7 +2852,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 245;
CURRENT_PROJECT_VERSION = 247;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -2913,7 +2913,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 245;
CURRENT_PROJECT_VERSION = 247;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;

View File

@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.25.0</string>
<string>1.25.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -34,7 +34,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>245</string>
<string>247</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.25.0</string>
<string>1.25.1</string>
<key>CFBundleVersion</key>
<string>245</string>
<string>247</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>

View File

@@ -25,6 +25,10 @@ class ShareViewController: SLComposeServiceViewController {
fileprivate var selectedChannel: Item?
fileprivate var selectedTeam: Item?
private var channelsVC: ChannelsViewController = ChannelsViewController()
private var teamsVC: TeamsViewController = TeamsViewController()
private var maxMessageSize: Int = 0
// MARK: - Lifecycle methods
override func viewDidLoad() {
@@ -80,6 +84,7 @@ class ShareViewController: SLComposeServiceViewController {
entities = store.getEntities(true) as [AnyHashable:Any]?
sessionToken = store.getToken()
serverURL = store.getServerUrl()
maxMessageSize = Int(store.getMaxPostSize())
extractDataFromContext()
@@ -89,28 +94,35 @@ class ShareViewController: SLComposeServiceViewController {
}
override func isContentValid() -> Bool {
let maxMessageSize = store.getMaxPostSize()
self.charactersRemaining = NSNumber(value: Int(maxMessageSize) - contentText.count)
//Check content text size is not above max
if (contentText.count > maxMessageSize) {
if !maxPostAlertShown {
maxPostAlertShown = true
showErrorMessageAndStayOpen(title: "", message: "Content text shared in Mattermost must be less than \(maxMessageSize+1) characters.", VC: self)
if let currentMessage = contentText {
let contentCount = currentMessage.count
if #available(iOS 13, *) {} else {
let remaining = (maxMessageSize - contentCount) as NSNumber
// this is causing the extension to run OOM on iOS 13
charactersRemaining = remaining
}
return false
} else if (attachments.count > 0) { // Do validation of contentText and/or NSExtensionContext attachments here
let maxImagePixels = store.getMaxImagePixels()
if attachments.hasImageLargerThan(pixels: maxImagePixels) {
let readableMaxImagePixels = formatImagePixels(pixels: maxImagePixels)
showErrorMessage(title: "", message: "Image attachments shared in Mattermost must be less than \(readableMaxImagePixels).", VC: self)
}
let maxFileSize = store.getMaxFileSize()
if attachments.hasAttachementLargerThan(fileSize: maxFileSize) {
let readableMaxFileSize = formatFileSize(fileSize: maxFileSize)
showErrorMessage(title: "", message: "File attachments shared in Mattermost must be less than \(readableMaxFileSize).", VC: self)
//Check content text size is not above max
if (contentCount > maxMessageSize) {
if !maxPostAlertShown {
maxPostAlertShown = true
showErrorMessageAndStayOpen(title: "", message: "Content text shared in Mattermost must be less than \(maxMessageSize+1) characters.", VC: self)
}
return false
} else if (attachments.count > 0) { // Do validation of contentText and/or NSExtensionContext attachments here
let maxImagePixels = store.getMaxImagePixels()
if attachments.hasImageLargerThan(pixels: maxImagePixels) {
let readableMaxImagePixels = formatImagePixels(pixels: maxImagePixels)
showErrorMessage(title: "", message: "Image attachments shared in Mattermost must be less than \(readableMaxImagePixels).", VC: self)
}
let maxFileSize = store.getMaxFileSize()
if attachments.hasAttachementLargerThan(fileSize: maxFileSize) {
let readableMaxFileSize = formatFileSize(fileSize: maxFileSize)
showErrorMessage(title: "", message: "File attachments shared in Mattermost must be less than \(readableMaxFileSize).", VC: self)
}
}
}
return serverURL != nil &&
sessionToken != nil &&
attachmentsCount() == attachments.count &&
@@ -166,10 +178,9 @@ class ShareViewController: SLComposeServiceViewController {
teams.title = "Team"
teams.value = selectedTeam?.title
teams.tapHandler = {
let vc = TeamsViewController()
vc.teamDecks = teamDecks
vc.delegate = self
self.pushConfigurationViewController(vc)
self.teamsVC.teamDecks = teamDecks
self.teamsVC.delegate = self
self.pushConfigurationViewController(self.teamsVC)
}
items.append(teams)
}
@@ -180,11 +191,10 @@ class ShareViewController: SLComposeServiceViewController {
channels.value = selectedChannel?.title
channels.valuePending = channelDecks == nil
channels.tapHandler = {
let vc = ChannelsViewController()
vc.channelDecks = channelDecks!
vc.navbarTitle = self.selectedTeam?.title
vc.delegate = self
self.pushConfigurationViewController(vc)
self.channelsVC.channelDecks = channelDecks!
self.channelsVC.navbarTitle = self.selectedTeam?.title
self.channelsVC.delegate = self
self.pushConfigurationViewController(self.channelsVC)
}
items.append(channels)
@@ -223,7 +233,10 @@ class ShareViewController: SLComposeServiceViewController {
if id == currentChannelId {
item.selected = true
selectedChannel = item
placeholder = "Write to \(item.title!)"
if #available(iOS 13, *) {} else {
// this is causing the extension to run OOM on iOS 13
self.placeholder = "Write to \(item.title!)"
}
}
section.items.append(item)
}
@@ -398,14 +411,14 @@ class ShareViewController: SLComposeServiceViewController {
key: "public",
title: "Public Channels"
))
channelDecks.append(buildChannelSection(
channels: channelsInTeamBySections.object(forKey: "private") as! NSArray,
currentChannelId: selectedChannel?.id ?? currentChannel?.object(forKey: "id") as! String,
key: "private",
title: "Private Channels"
))
channelDecks.append(buildChannelSection(
channels: channelsInTeamBySections.object(forKey: "direct") as! NSArray,
currentChannelId: selectedChannel?.id ?? currentChannel?.object(forKey: "id") as! String,

View File

@@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.25.0</string>
<string>1.25.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>245</string>
<string>247</string>
</dict>
</plist>

View File

@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.25.0</string>
<string>1.25.1</string>
<key>CFBundleVersion</key>
<string>245</string>
<string>247</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>

879
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "mattermost-mobile",
"version": "1.25.0",
"version": "1.25.1",
"description": "Mattermost Mobile with React Native",
"repository": "git@github.com:mattermost/mattermost-mobile.git",
"author": "Mattermost, Inc.",
@@ -33,7 +33,7 @@
"react-native-button": "2.4.0",
"react-native-calendars": "github:mattermost/react-native-calendars#4937ec5a3bf7e86f9f35fcd85eb4aa6133f45b58",
"react-native-circular-progress": "1.1.0",
"react-native-cookies": "github:joeferraro/react-native-cookies#f11374745deba9f18f7b8a9bb4b0b2573026f522",
"react-native-cookies": "github:mattermost/react-native-cookies#a54c0ffd28679871dd11e3396d8382cc8ff204d1",
"react-native-device-info": "github:mattermost/react-native-device-info#f7175f10822d8f66b9806206e3313eaf2f4aabc6",
"react-native-doc-viewer": "2.7.8",
"react-native-document-picker": "2.3.0",
@@ -62,6 +62,7 @@
"react-native-webview": "github:mattermost/react-native-webview#b5e22940a613869d3999feac9451ee65352f4fbe",
"react-native-youtube": "github:mattermost/react-native-youtube#22954c394146ec9a09ce5510056c17714a7155b2",
"react-navigation": "3.9.1",
"react-navigation-stack": "1.3.0",
"react-redux": "7.0.3",
"redux": "4.0.1",
"redux-batched-actions": "0.4.1",