Appconfig support for iOS and Android (#856)

* AppConfig support for iOS

* AppConfig support for Android

* Fix typo

* Java feedback review
This commit is contained in:
enahum 2017-08-18 13:19:00 -03:00 committed by Harrison Healey
parent 2b33618f31
commit cd5fb71681
29 changed files with 1458 additions and 39 deletions

View file

@ -1154,3 +1154,96 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## react-native-passcode-status
This product contains 'react-native-passcode-status', A thin wrapper around UIDevice-PasscodeStatus.
* HOMEPAGE:
* https://github.com/tradle/react-native-passcode-status
* LICENSE:
The MIT License (MIT)
Copyright (c) 2015 Tradle
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
## react-native-local-auth
This product contains 'react-native-local-auth', Authenticate users with Touch ID, with optional fallback to passcode.
* HOMEPAGE:
* https://github.com/tradle/react-native-local-auth
* LICENSE:
ISC License
Copyright (c) 2015, [Tradle, Inc](http://tradle.io/)
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---
## jail-monkey
This product contains 'jail-monkey', Identify if a phone has been jail-broken or rooted for iOS/Android.
* HOMEPAGE:
* https://github.com/GantMan/jail-monkey/
* LICENSE:
MIT License
Copyright (c) 2017 Gant Laborde
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -160,6 +160,8 @@ dependencies {
compile project(':react-native-linear-gradient')
compile project(':react-native-vector-icons')
compile project(':react-native-svg')
compile project(':react-native-local-auth')
compile project(':jail-monkey')
}
// Run this once to be able to run the application with BUCK

View file

@ -29,7 +29,10 @@
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme"
>
<meta-data android:name="com.wix.reactnativenotifications.gcmSenderId" android:value="184930218130\0"/>
<meta-data android:name="android.content.APP_RESTRICTIONS"
android:resource="@xml/app_restrictions" />
<meta-data android:name="com.wix.reactnativenotifications.gcmSenderId" android:value="184930218130\0"/>
<activity
android:name=".MainActivity"
android:label="@string/app_name"

View file

@ -1,9 +1,5 @@
package com.mattermost.rnbeta;
import com.github.yamill.orientation.OrientationPackage;
import com.psykar.cookiemanager.CookieManagerPackage;
import com.BV.LinearGradient.LinearGradientPackage;
import com.reactnativenavigation.controllers.SplashActivity;
import java.lang.ref.WeakReference;
@ -37,23 +33,23 @@ public class MainActivity extends SplashActivity {
@Override
public LinearLayout createSplashLayout() {
wr_activity = new WeakReference<>(this);
LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
Context context = getActivity();
final int drawableId = getImageId();
Context context = getActivity();
NotificationsLifecycleFacade.getInstance().LoadManagedConfig(getActivity());
imageView = new ImageView(context);
imageView.setImageResource(drawableId);
LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
imageView.setLayoutParams(layoutParams);
imageView.setScaleType(ImageView.ScaleType.CENTER);
LinearLayout view = new LinearLayout(this);
view.setBackgroundColor(Color.parseColor("#FFFFFF"));
view.setGravity(Gravity.CENTER);
view.addView(imageView);
return view;
}

View file

@ -1,12 +1,13 @@
package com.mattermost.rnbeta;
import android.app.Application;
import android.util.Log;
import android.support.annotation.NonNull;
import android.content.Context;
import android.os.Bundle;
import com.facebook.react.ReactApplication;
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;
@ -33,8 +34,7 @@ import java.util.Arrays;
import java.util.List;
public class MainApplication extends NavigationApplication implements INotificationsApplication {
NotificationsLifecycleFacade notificationsLifecycleFacade;
public NotificationsLifecycleFacade notificationsLifecycleFacade;
@Override
public boolean isDebug() {
@ -55,20 +55,22 @@ public class MainApplication extends NavigationApplication implements INotificat
new SvgPackage(),
new LinearGradientPackage(),
new OrientationPackage(),
new RNNotificationsPackage(MainApplication.this)
new RNNotificationsPackage(this),
new LocalAuthPackage(),
new JailMonkeyPackage(),
new MattermostManagedPackage()
);
}
@Override
public void onCreate() {
super.onCreate();
instance = this;
// Create an object of the custom facade impl
notificationsLifecycleFacade = new NotificationsLifecycleFacade();
notificationsLifecycleFacade = NotificationsLifecycleFacade.getInstance();
// Attach it to react-native-navigation
setActivityCallbacks(notificationsLifecycleFacade);
SoLoader.init(this, /* native exopackage */ false);
}

View file

@ -0,0 +1,65 @@
package com.mattermost.rnbeta;
import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
public class MattermostManagedModule extends ReactContextBaseJavaModule {
private static MattermostManagedModule instance;
private boolean shouldBlurAppScreen = false;
private MattermostManagedModule(ReactApplicationContext reactContext) {
super(reactContext);
}
public static MattermostManagedModule getInstance(ReactApplicationContext reactContext) {
if (instance == null) {
instance = new MattermostManagedModule(reactContext);
}
return instance;
}
public static MattermostManagedModule getInstance() {
return instance;
}
@Override
public String getName() {
return "MattermostManaged";
}
@ReactMethod
public void blurAppScreen(boolean enabled) {
shouldBlurAppScreen = enabled;
}
public boolean isBlurAppScreenEnabled() {
return shouldBlurAppScreen;
}
@ReactMethod
public void getConfig(final Promise promise) {
try {
Bundle config = NotificationsLifecycleFacade.getInstance().getManagedConfig();
if (config != null) {
Object result = Arguments.fromBundle(config);
promise.resolve(result);
} else {
throw new Exception("The MDM vendor has not sent any Managed configuration");
}
} catch (Exception e) {
promise.reject("no managed configuration", e);
}
}
}

View file

@ -0,0 +1,28 @@
package com.mattermost.rnbeta;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.JavaScriptModule;
public class MattermostManagedPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(MattermostManagedModule.getInstance(reactContext));
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View file

@ -1,9 +1,20 @@
package com.mattermost.rnbeta;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.RestrictionsManager;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.util.ArraySet;
import android.view.WindowManager.LayoutParams;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.reactnativenavigation.NavigationApplication;
import com.reactnativenavigation.controllers.ActivityCallbacks;
import com.reactnativenavigation.react.ReactGateway;
@ -12,16 +23,72 @@ import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class NotificationsLifecycleFacade extends ActivityCallbacks implements AppLifecycleFacade {
private static final String TAG = NotificationsLifecycleFacade.class.getSimpleName();
private static NotificationsLifecycleFacade instance;
private Bundle managedConfig = null;
private Activity mVisibleActivity;
private Set<AppVisibilityListener> mListeners = new CopyOnWriteArraySet<>();
private final IntentFilter restrictionsFilter =
new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
private final BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
if (mVisibleActivity != null) {
// Get the current configuration bundle
RestrictionsManager myRestrictionsMgr =
(RestrictionsManager) mVisibleActivity
.getSystemService(Context.RESTRICTIONS_SERVICE);
managedConfig = myRestrictionsMgr.getApplicationRestrictions();
// Check current configuration settings, change your app's UI and
// functionality as necessary.
Log.i("ReactNative", "Managed Configuration Changed");
sendConfigChanged(managedConfig);
}
}
};
public static NotificationsLifecycleFacade getInstance() {
if (instance == null) {
instance = new NotificationsLifecycleFacade();
}
return instance;
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
MattermostManagedModule managedModule = MattermostManagedModule.getInstance();
if (managedModule != null && managedModule.isBlurAppScreenEnabled()) {
activity.getWindow().setFlags(LayoutParams.FLAG_SECURE,
LayoutParams.FLAG_SECURE);
}
if (managedConfig!= null && managedConfig.size() > 0) {
activity.registerReceiver(restrictionsReceiver, restrictionsFilter);
}
}
@Override
public void onActivityResumed(Activity activity) {
switchToVisible(activity);
if (managedConfig != null && managedConfig.size() > 0) {
RestrictionsManager myRestrictionsMgr =
(RestrictionsManager) activity
.getSystemService(Context.RESTRICTIONS_SERVICE);
Bundle newConfig = myRestrictionsMgr.getApplicationRestrictions();
if (!equalBundles(newConfig ,managedConfig)) {
Log.i("ReactNative", "onResumed Managed Configuration Changed");
managedConfig = newConfig;
sendConfigChanged(managedConfig);
}
}
}
@Override
@ -32,6 +99,13 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A
@Override
public void onActivityStopped(Activity activity) {
switchToInvisible(activity);
if (managedConfig != null && managedConfig.size() > 0) {
try {
activity.unregisterReceiver(restrictionsReceiver);
} catch (IllegalArgumentException e) {
// Just ignore this cause the receiver wasn't registered for this activity
}
}
}
@Override
@ -88,4 +162,67 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A
}
}
}
public synchronized void LoadManagedConfig(Activity activity) {
RestrictionsManager myRestrictionsMgr =
(RestrictionsManager) activity
.getSystemService(Context.RESTRICTIONS_SERVICE);
managedConfig = myRestrictionsMgr.getApplicationRestrictions();
myRestrictionsMgr = null;
}
public synchronized Bundle getManagedConfig() {
if (managedConfig!= null && managedConfig.size() > 0) {
return managedConfig;
}
if (mVisibleActivity != null) {
LoadManagedConfig(mVisibleActivity);
return managedConfig;
}
return null;
}
public void sendConfigChanged(Bundle config) {
Object result = Arguments.fromBundle(config);
getRunningReactContext().
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

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<restrictions xmlns:android="http://schemas.android.com/apk/res/android">
<restriction
android:key="inAppPinCode"
android:restrictionType="string"
android:defaultValue="false" />
<restriction
android:key="blurApplicationScreen"
android:restrictionType="string"
android:defaultValue="false" />
<restriction
android:key="jailbreakProtection"
android:restrictionType="string"
android:defaultValue="false" />
<restriction
android:key="copyAndPasteProtection"
android:restrictionType="string"
android:defaultValue="false" />
<restriction
android:key="serverUrl"
android:restrictionType="string"
android:defaultValue="" />
<restriction
android:key="allowOtherServers"
android:restrictionType="string"
android:defaultValue="true" />
<restriction
android:key="username"
android:restrictionType="string"
android:defaultValue="" />
<restriction
android:key="vendor"
android:restrictionType="string"
android:defaultValue="" />
</restrictions>

View file

@ -1,4 +1,8 @@
rootProject.name = 'Mattermost'
include ':jail-monkey'
project(':jail-monkey').projectDir = new File(rootProject.projectDir, '../node_modules/jail-monkey/android')
include ':react-native-local-auth'
project(':react-native-local-auth').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-local-auth/android')
include ':react-native-navigation'
project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/android/app/')
include ':react-native-image-picker'

View file

@ -28,19 +28,30 @@ import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
import {Client, Client4} from 'mattermost-redux/client';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {goToNotification, loadConfigAndLicense, queueNotification, setStatusBarHeight, purgeOfflineStore} from 'app/actions/views/root';
import {
goToNotification,
loadConfigAndLicense,
queueNotification,
setStatusBarHeight,
purgeOfflineStore
} from 'app/actions/views/root';
import {setChannelDisplayName} from 'app/actions/views/channel';
import {handleLoginIdChanged} from 'app/actions/views/login';
import {handleServerUrlChanged} from 'app/actions/views/select_server';
import {NavigationTypes, ViewTypes} from 'app/constants';
import {getTranslations} from 'app/i18n';
import initialState from 'app/initial_state';
import PushNotifications from 'app/push_notifications';
import {registerScreens} from 'app/screens';
import configureStore from 'app/store';
import mattermostManaged from 'app/mattermost_managed';
import Config from 'assets/config';
const {StatusBarManager} = NativeModules;
const store = configureStore(initialState);
const AUTHENTICATION_TIMEOUT = 5 * 60 * 1000;
registerScreens(store, Provider);
export default class Mattermost {
@ -52,6 +63,7 @@ export default class Mattermost {
console.ignoredYellowBox = ['`scaleY`']; //eslint-disable-line
}
this.isConfigured = false;
this.allowOtherServers = true;
setJSExceptionHandler(this.errorHandler, false);
Orientation.lockToPortrait();
this.unsubscribeFromStore = store.subscribe(this.listenForHydration);
@ -61,6 +73,8 @@ export default class Mattermost {
EventEmitter.on(General.DEFAULT_CHANNEL, this.handleResetDisplayName);
EventEmitter.on(NavigationTypes.RESTART_APP, this.restartApp);
mattermostManaged.addEventListener('managedConfigDidChange', this.handleManagedConfig);
this.handleAppStateChange(AppState.currentState);
Client4.setUserAgent(DeviceInfo.getUserAgent());
@ -140,9 +154,52 @@ export default class Mattermost {
});
};
handleAppStateChange = (appState) => {
handleAppStateChange = async (appState) => {
const {dispatch, getState} = store;
setAppState(appState === 'active')(dispatch, getState);
const isActive = appState === 'active';
setAppState(isActive)(dispatch, getState);
try {
if (!isActive && !this.inBackgroundSince) {
this.inBackgroundSince = Date.now();
} else if (isActive && this.inBackgroundSince && (Date.now() - this.inBackgroundSince) >= AUTHENTICATION_TIMEOUT) {
this.inBackgroundSince = null;
if (this.mdmEnabled) {
const config = await mattermostManaged.getConfig();
const authNeeded = config.inAppPinCode && config.inAppPinCode === 'true';
if (authNeeded) {
const authenticated = await this.handleAuthentication(config.vendor);
if (!authenticated) {
mattermostManaged.quitApp();
}
}
}
}
} catch (error) {
// do nothing
}
};
handleAuthentication = async (vendor) => {
const isSecured = await mattermostManaged.isDeviceSecure();
const intl = this.getIntl();
if (isSecured) {
try {
await mattermostManaged.authenticate({
reason: intl.formatMessage({
id: 'mobile.managed.secured_by',
defaultMessage: 'Secured by {vendor}'
}, {vendor}),
fallbackToPasscode: true,
suppressEnterPassword: true
});
} catch (err) {
mattermostManaged.quitApp();
return false;
}
}
return true;
};
handleConfigChanged = async (serverVersion) => {
@ -169,6 +226,94 @@ export default class Mattermost {
}
};
handleManagedConfig = async (serverConfig) => {
const {dispatch, getState} = store;
const state = getState();
let authNeeded = false;
let blurApplicationScreen = false;
let jailbreakProtection = false;
let vendor = null;
let serverUrl = null;
let username = null;
try {
const config = await mattermostManaged.getConfig();
if (config) {
this.mdmEnabled = true;
authNeeded = config.inAppPinCode && config.inAppPinCode === 'true';
blurApplicationScreen = config.blurApplicationScreen && config.blurApplicationScreen === 'true';
jailbreakProtection = config.jailbreakProtection && config.jailbreakProtection === 'true';
vendor = config.vendor || 'Mattermost';
if (!state.entities.general.credentials.token) {
serverUrl = config.serverUrl;
username = config.username;
if (config.allowOtherServers && config.allowOtherServers === 'false') {
this.allowOtherServers = false;
}
}
}
} catch (error) {
return true;
}
if (this.mdmEnabled) {
if (jailbreakProtection) {
const isTrusted = mattermostManaged.isTrustedDevice();
if (!isTrusted) {
const intl = this.getIntl();
Alert.alert(
intl.formatMessage({
id: 'mobile.managed.blocked_by',
defaultMessage: 'Blocked by {vendor}'
}, {vendor}),
intl.formatMessage({
id: 'mobile.managed.jailbreak',
defaultMessage: 'Jailbroken devices are not trusted by {vendor}, please exit the app.'
}, {vendor}),
[{
text: intl.formatMessage({id: 'mobile.managed.exit', defaultMessage: 'Exit'}),
style: 'destructive',
onPress: () => {
mattermostManaged.quitApp();
}
}],
{cancelable: false}
);
return false;
}
}
if (authNeeded && !serverConfig) {
if (Platform.OS === 'android') {
//Start a fake app as we need at least one activity for android
await this.startFakeApp();
}
const authenticated = await this.handleAuthentication(vendor);
if (!authenticated) {
return false;
}
}
if (blurApplicationScreen) {
mattermostManaged.blurAppScreen(true);
}
if (serverUrl) {
handleServerUrlChanged(serverUrl)(dispatch, getState);
}
if (username) {
handleLoginIdChanged(username)(dispatch, getState);
}
}
return true;
};
handleReset = () => {
this.resetBadgeAndVersion();
this.startApp('fade');
@ -202,7 +347,11 @@ export default class Mattermost {
const state = store.getState();
if (state.views.root.hydrationComplete) {
this.unsubscribeFromStore();
this.startApp();
this.handleManagedConfig().then((shouldStart) => {
if (shouldStart) {
this.startApp();
}
});
}
};
@ -274,6 +423,22 @@ export default class Mattermost {
this.startApp('fade');
};
startFakeApp = async () => {
return Navigation.startSingleScreenApp({
screen: {
screen: 'Root',
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarHideWithNavBar: false
}
},
passProps: {
justInit: true
}
});
};
startApp = (animationType = 'none') => {
Navigation.startSingleScreenApp({
screen: {
@ -284,6 +449,9 @@ export default class Mattermost {
statusBarHideWithNavBar: false
}
},
passProps: {
allowOtherServers: this.allowOtherServers
},
animationType
});

View file

@ -0,0 +1,6 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
// Used to leverage the platform specific components
import MattermostManaged from './mattermost-managed';
export default MattermostManaged;

View file

@ -0,0 +1,36 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {BackHandler, NativeModules, DeviceEventEmitter} from 'react-native';
import LocalAuth from 'react-native-local-auth';
import JailMonkey from 'jail-monkey';
const {MattermostManaged} = NativeModules;
export default {
addEventListener: (name, callback) => {
DeviceEventEmitter.addListener(name, (config) => {
if (callback && typeof callback === 'function') {
callback(config);
}
});
},
authenticate: LocalAuth.authenticate,
blurAppScreen: MattermostManaged.blurAppScreen,
getConfig: MattermostManaged.getConfig,
isDeviceSecure: async () => {
try {
return await LocalAuth.isDeviceSecure();
} catch (err) {
return false;
}
},
isTrustedDevice: () => {
if (__DEV__) { //eslint-disable-line no-undef
return true;
}
return JailMonkey.trustFall();
},
quitApp: BackHandler.exitApp
};

View file

@ -0,0 +1,36 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {NativeModules, NativeEventEmitter} from 'react-native';
import LocalAuth from 'react-native-local-auth';
import JailMonkey from 'jail-monkey';
const {BlurAppScreen, MattermostManaged} = NativeModules;
const MattermostManagedEvents = new NativeEventEmitter(MattermostManaged);
export default {
addEventListener: (name, callback) => {
MattermostManagedEvents.addListener(name, (config) => {
if (callback && typeof callback === 'function') {
callback(config);
}
});
},
authenticate: LocalAuth.authenticate,
blurAppScreen: BlurAppScreen.enabled,
getConfig: MattermostManaged.getConfig,
isDeviceSecure: async () => {
try {
return await LocalAuth.isDeviceSecure();
} catch (err) {
return false;
}
},
isTrustedDevice: () => {
if (__DEV__) { //eslint-disable-line no-undef
return true;
}
return JailMonkey.trustFall();
},
quitApp: MattermostManaged.quitApp
};

View file

@ -12,7 +12,9 @@ import {stripTrailingSlashes} from 'app/utils/url';
export default class Root extends PureComponent {
static propTypes = {
allowOtherServers: PropTypes.bool,
credentials: PropTypes.object,
justInit: PropTypes.bool,
loginRequest: PropTypes.object,
navigator: PropTypes.object,
theme: PropTypes.object,
@ -22,7 +24,9 @@ export default class Root extends PureComponent {
};
componentDidMount() {
this.loadStoreAndScene();
if (!this.props.justInit) {
this.loadStoreAndScene();
}
}
goToLoadTeam = () => {
@ -44,7 +48,9 @@ export default class Root extends PureComponent {
};
goToSelectServer = () => {
this.props.navigator.resetTo({
const {allowOtherServers, navigator} = this.props;
navigator.resetTo({
screen: 'SelectServer',
animated: false,
navigatorStyle: {
@ -52,6 +58,9 @@ export default class Root extends PureComponent {
navBarBackgroundColor: 'black',
statusBarHidden: false,
statusBarHideWithNavBar: false
},
passProps: {
allowOtherServers
}
});
};

View file

@ -32,6 +32,7 @@ import logo from 'assets/images/logo.png';
class SelectServer extends PureComponent {
static propTypes = {
allowOtherServers: PropTypes.bool,
navigator: PropTypes.object,
intl: intlShape.isRequired,
config: PropTypes.object,
@ -59,6 +60,13 @@ class SelectServer extends PureComponent {
}
componentDidMount() {
const {allowOtherServers, pingRequest, serverUrl} = this.props;
if (pingRequest.status === RequestStatus.NOT_STARTED && !allowOtherServers && serverUrl) {
// If the app is managed, the server url is set and the user can't change it
// we automatically trigger the ping to move to the next screen
this.onClick();
}
if (Platform.OS === 'android') {
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
}
@ -151,7 +159,7 @@ class SelectServer extends PureComponent {
};
render() {
const {serverUrl, pingRequest, configRequest, licenseRequest} = this.props;
const {allowOtherServers, serverUrl, pingRequest, configRequest, licenseRequest} = this.props;
const isLoading = pingRequest.status === RequestStatus.STARTED ||
configRequest.status === RequestStatus.STARTED ||
licenseRequest.status === RequestStatus.STARTED;
@ -203,9 +211,10 @@ class SelectServer extends PureComponent {
<TextInputWithLocalizedPlaceholder
ref={this.inputRef}
value={serverUrl}
editable={allowOtherServers}
onChangeText={this.props.actions.handleServerUrlChanged}
onSubmitEditing={this.onClick}
style={GlobalStyles.inputBox}
style={[GlobalStyles.inputBox, allowOtherServers ? {} : {backgroundColor: '#e3e3e3'}]}
autoCapitalize='none'
autoCorrect={false}
keyboardType='url'

View file

@ -1845,6 +1845,10 @@
"mobile.loading_members": "Loading Members...",
"mobile.loading_posts": "Loading Messages...",
"mobile.login_options.choose_title": "Choose your login method",
"mobile.managed.blocked_by": "Blocked by {vendor}",
"mobile.managed.exit": "Exit",
"mobile.managed.jailbreak": "Jailbroken devices are not trusted by {vendor}, please exit the app.",
"mobile.managed.secured_by": "Secured by {vendor}",
"mobile.more_dms.start": "Start",
"mobile.more_dms.title": "New Conversation",
"mobile.notice_mobile_link": "mobile apps",

View file

@ -355,6 +355,18 @@ platform :android do
new_string: 'package com.mattermost.rn;'
)
find_replace_string(
path_to_file: './android/app/src/main/java/com/mattermost/rn/MattermostManagedModule.java',
old_string: 'package com.mattermost.rnbeta;',
new_string: 'package com.mattermost.rn;'
)
find_replace_string(
path_to_file: './android/app/src/main/java/com/mattermost/rn/MattermostManagedPackage.java',
old_string: 'package com.mattermost.rnbeta;',
new_string: 'package com.mattermost.rn;'
)
find_replace_string(
path_to_file: './android/app/BUCK',
old_string: 'package com.mattermost.rnbeta;',

View file

@ -16,7 +16,7 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.2.1)
excon (0.58.0)
faraday (0.12.2)
faraday (0.13.0)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
@ -24,7 +24,7 @@ GEM
faraday_middleware (0.12.2)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.0)
fastlane (2.50.1)
fastlane (2.53.1)
CFPropertyList (>= 2.3, < 3.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)

13
ios/BlurAppScreen.h Normal file
View file

@ -0,0 +1,13 @@
//
// BlurAppScreen.h
// Mattermost
//
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
//
#import <React/RCTBridge.h>
@interface BlurAppScreen : NSObject <RCTBridgeModule>
@end

75
ios/BlurAppScreen.m Normal file
View file

@ -0,0 +1,75 @@
//
// BlurAppScreen.m
// Mattermost
//
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
//
#import "BlurAppScreen.h"
#import "UIImage+ImageEffects.h"
@implementation BlurAppScreen{
BOOL enabled;
UIImageView *obfuscatingView;
}
RCT_EXPORT_MODULE();
- (instancetype)init {
if ((self = [super init])) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAppStateResignActive)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAppStateActive)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void)handleAppStateResignActive {
if (self->enabled) {
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
UIImageView *blurredScreenImageView = [[UIImageView alloc] initWithFrame:keyWindow.bounds];
UIGraphicsBeginImageContext(keyWindow.bounds.size);
[keyWindow drawViewHierarchyInRect:keyWindow.frame afterScreenUpdates:NO];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
blurredScreenImageView.image = [viewImage applyLightEffect];
self->obfuscatingView = blurredScreenImageView;
[[UIApplication sharedApplication].keyWindow addSubview:self->obfuscatingView];
}
}
- (void)handleAppStateActive {
if (self->obfuscatingView) {
[UIView animateWithDuration: 0.3
animations: ^ {
self->obfuscatingView.alpha = 0;
}
completion: ^(BOOL finished) {
[self->obfuscatingView removeFromSuperview];
self->obfuscatingView = nil;
}
];
}
}
RCT_EXPORT_METHOD(enabled:(BOOL) _enable) {
self->enabled = _enable;
}
@end

View file

@ -30,18 +30,23 @@
2E3655C7E15049DBBC3A666B /* libRNImagePicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DFE628F5A526417D85AD635E /* libRNImagePicker.a */; };
374634801E8480C2005E1244 /* libRCTOrientation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 374634671E848085005E1244 /* libRCTOrientation.a */; };
382D94CF15EE4FC292C3F341 /* Foundation.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 51F5A483EA9F4A9A87ACFB59 /* Foundation.ttf */; };
38FB8B0A3AAD4F839EF6EF0B /* libJailMonkey.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 77810F0A063349439B0D8B6B /* libJailMonkey.a */; };
3D38ABA732A34A9BB3294F90 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 349FBA7338E74D9BBD709528 /* EvilIcons.ttf */; };
420A7328E12C4B72AEF420CE /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EDC04CBCF81642219D199CBB /* Octicons.ttf */; };
55C6561DDBBA45929D88B6D1 /* OpenSans-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 32AC3D4EA79E44738A6E9766 /* OpenSans-BoldItalic.ttf */; };
584837D6B55F405F908A2053 /* libBVLinearGradient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ACC6B9FDC0AD45A6BFA4FBCD /* libBVLinearGradient.a */; };
5E1AF7B72B8D4A4E9E53FF9D /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 005346E5C0E542BFABAE1411 /* FontAwesome.ttf */; };
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
615D2E7805814A3A9B9C69EC /* libRNLocalAuth.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EE3EE4548D3F4A49B1274722 /* libRNLocalAuth.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 */; };
7F1B28A31F3A76BB00701A6A /* MattermostManaged.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F1B28A21F3A76BB00701A6A /* MattermostManaged.m */; };
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 */; };
7F292AA71E8ABB1100A450A3 /* splash.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F292AA51E8ABB1100A450A3 /* splash.png */; };
7F5052F61F3F3C1C00FE9812 /* UIImage+ImageEffects.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F5052F51F3F3C1C00FE9812 /* UIImage+ImageEffects.m */; };
7F5053221F3F57AC00FE9812 /* BlurAppScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F5053211F3F57AC00FE9812 /* BlurAppScreen.m */; };
7F6877B31E7836070094B63F /* libToolTipMenu.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F6877B01E7835E50094B63F /* libToolTipMenu.a */; };
7FBB5E9A1E1F5A4B000DE18A /* libRNSVG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF28E11E1F4B1F00DBBE56 /* libRNSVG.a */; };
7FBB5E9B1E1F5A4B000DE18A /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF290C1E1F4B4E00DBBE56 /* libRNVectorIcons.a */; };
@ -56,6 +61,7 @@
D719A67137964F08BE47A5FC /* OpenSans-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3647DF63D6764CF093375861 /* OpenSans-ExtraBold.ttf */; };
F006936FC2884C24A1321FC0 /* MaterialIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 356C9186FA374641A00EB2EA /* MaterialIcons.ttf */; };
F083DB472349411A8E6E7AAD /* OpenSans-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BE17F630DB5D41FD93F32D22 /* OpenSans-LightItalic.ttf */; };
FDEF24E9D9AB47728E11033B /* libRNPasscodeStatus.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17A75F5A3D0D4A4A995DCD76 /* libRNPasscodeStatus.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -297,6 +303,27 @@
remoteGlobalIDString = 4681C02C1B05271A004D67D4;
remoteInfo = ToolTipMenuTests;
};
7F8C49861F3DFC30003A22BA /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D0281D64B98143668D6AD42B /* RNLocalAuth.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RNLocalAuth;
};
7F8C49F71F3E0710003A22BA /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = EBA6063A99C141098D40C67A /* RNPasscodeStatus.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 732D5BB41C30EA5700CA0A32;
remoteInfo = RNPasscodeStatus;
};
7F8C4A611F3E21FB003A22BA /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 27A6EA89298440439DA9F98D /* JailMonkey.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 38C138441D3FC3F600A8162D;
remoteInfo = JailMonkey;
};
7F906D961E6A1ADF00B8F49A /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 31A0780E2F224AC8AF8E6930 /* RNCookieManagerIOS.xcodeproj */;
@ -369,6 +396,8 @@
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Mattermost/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Mattermost/main.m; sourceTree = "<group>"; };
146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = "<group>"; };
17A75F5A3D0D4A4A995DCD76 /* libRNPasscodeStatus.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNPasscodeStatus.a; sourceTree = "<group>"; };
27A6EA89298440439DA9F98D /* JailMonkey.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = JailMonkey.xcodeproj; path = "../node_modules/jail-monkey/JailMonkey.xcodeproj"; sourceTree = "<group>"; };
2B25899FDAC149EB96ED3305 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = "<group>"; };
2CBE9C0FB56E4FDA96C30792 /* RNSVG.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSVG.xcodeproj; path = "../node_modules/react-native-svg/ios/RNSVG.xcodeproj"; sourceTree = "<group>"; };
2DCFD31D3F4A4154822AB532 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = "<group>"; };
@ -382,12 +411,19 @@
5C3B95629BA74FB3A8377CB7 /* RCTOrientation.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTOrientation.xcodeproj; path = "../node_modules/react-native-orientation/iOS/RCTOrientation.xcodeproj"; sourceTree = "<group>"; };
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = "../node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj"; sourceTree = "<group>"; };
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>"; };
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>"; };
7DCC3D826CE640AF8F491692 /* BVLinearGradient.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = BVLinearGradient.xcodeproj; path = "../node_modules/react-native-linear-gradient/BVLinearGradient.xcodeproj"; sourceTree = "<group>"; };
7F1B28A11F3A76BB00701A6A /* MattermostManaged.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MattermostManaged.h; sourceTree = "<group>"; };
7F1B28A21F3A76BB00701A6A /* MattermostManaged.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MattermostManaged.m; sourceTree = "<group>"; };
7F26919B1EE1DC51007574FE /* RNNotifications.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNNotifications.xcodeproj; path = "../node_modules/react-native-notifications/RNNotifications/RNNotifications.xcodeproj"; sourceTree = "<group>"; };
7F292A701E8AB73400A450A3 /* SplashScreenResource */ = {isa = PBXFileReference; lastKnownFileType = folder; path = SplashScreenResource; sourceTree = "<group>"; };
7F292AA41E8ABB1100A450A3 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = LaunchScreen.xib; path = SplashScreenResource/LaunchScreen.xib; sourceTree = "<group>"; };
7F292AA51E8ABB1100A450A3 /* splash.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = splash.png; path = SplashScreenResource/splash.png; sourceTree = "<group>"; };
7F5052F41F3F3C1C00FE9812 /* UIImage+ImageEffects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ImageEffects.h"; sourceTree = "<group>"; };
7F5052F51F3F3C1C00FE9812 /* UIImage+ImageEffects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ImageEffects.m"; sourceTree = "<group>"; };
7F5053201F3F57AC00FE9812 /* BlurAppScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlurAppScreen.h; sourceTree = "<group>"; };
7F5053211F3F57AC00FE9812 /* BlurAppScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlurAppScreen.m; sourceTree = "<group>"; };
7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = "<group>"; };
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = "<group>"; };
7F6877AA1E7835E50094B63F /* ToolTipMenu.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ToolTipMenu.xcodeproj; path = "../node_modules/react-native-tooltip/ToolTipMenu.xcodeproj"; sourceTree = "<group>"; };
@ -400,9 +436,12 @@
BC977883E2624E05975CA65B /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Regular.ttf"; path = "../assets/fonts/OpenSans-Regular.ttf"; sourceTree = "<group>"; };
BE17F630DB5D41FD93F32D22 /* OpenSans-LightItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-LightItalic.ttf"; path = "../assets/fonts/OpenSans-LightItalic.ttf"; sourceTree = "<group>"; };
C78A387124874496AD2C1466 /* OpenSans-Semibold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Semibold.ttf"; path = "../assets/fonts/OpenSans-Semibold.ttf"; sourceTree = "<group>"; };
D0281D64B98143668D6AD42B /* RNLocalAuth.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNLocalAuth.xcodeproj; path = "../node_modules/react-native-local-auth/RNLocalAuth.xcodeproj"; sourceTree = "<group>"; };
D4B1B363C2414DA19C1AC521 /* OpenSans-Bold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-Bold.ttf"; path = "../assets/fonts/OpenSans-Bold.ttf"; sourceTree = "<group>"; };
DFE628F5A526417D85AD635E /* libRNImagePicker.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNImagePicker.a; sourceTree = "<group>"; };
EBA6063A99C141098D40C67A /* RNPasscodeStatus.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNPasscodeStatus.xcodeproj; path = "../node_modules/react-native-passcode-status/ios/RNPasscodeStatus.xcodeproj"; sourceTree = "<group>"; };
EDC04CBCF81642219D199CBB /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; };
EE3EE4548D3F4A49B1274722 /* libRNLocalAuth.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNLocalAuth.a; sourceTree = "<group>"; };
EE671DF7637347CD8C069819 /* libRNDeviceInfo.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNDeviceInfo.a; sourceTree = "<group>"; };
F1F071EE85494E269A50AE88 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
FBBEC29EE2D3418D9AC33BD5 /* OpenSans-ExtraBoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "OpenSans-ExtraBoldItalic.ttf"; path = "../assets/fonts/OpenSans-ExtraBoldItalic.ttf"; sourceTree = "<group>"; };
@ -442,6 +481,9 @@
2E3655C7E15049DBBC3A666B /* libRNImagePicker.a in Frameworks */,
7FC200E81EBB65370099331B /* libReactNativeNavigation.a in Frameworks */,
7F2691A11EE1DC6A007574FE /* libRNNotifications.a in Frameworks */,
615D2E7805814A3A9B9C69EC /* libRNLocalAuth.a in Frameworks */,
FDEF24E9D9AB47728E11033B /* libRNPasscodeStatus.a in Frameworks */,
38FB8B0A3AAD4F839EF6EF0B /* libJailMonkey.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -563,6 +605,12 @@
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
13B07FB71A68108700A75B9A /* main.m */,
7F1B28A11F3A76BB00701A6A /* MattermostManaged.h */,
7F1B28A21F3A76BB00701A6A /* MattermostManaged.m */,
7F5052F41F3F3C1C00FE9812 /* UIImage+ImageEffects.h */,
7F5052F51F3F3C1C00FE9812 /* UIImage+ImageEffects.m */,
7F5053201F3F57AC00FE9812 /* BlurAppScreen.h */,
7F5053211F3F57AC00FE9812 /* BlurAppScreen.m */,
);
name = Mattermost;
sourceTree = "<group>";
@ -658,6 +706,30 @@
name = Products;
sourceTree = "<group>";
};
7F8C49621F3DFC30003A22BA /* Products */ = {
isa = PBXGroup;
children = (
7F8C49871F3DFC30003A22BA /* libRNLocalAuth.a */,
);
name = Products;
sourceTree = "<group>";
};
7F8C49D11F3E070F003A22BA /* Products */ = {
isa = PBXGroup;
children = (
7F8C49F81F3E0710003A22BA /* libRNPasscodeStatus.a */,
);
name = Products;
sourceTree = "<group>";
};
7F8C4A5D1F3E21FB003A22BA /* Products */ = {
isa = PBXGroup;
children = (
7F8C4A621F3E21FB003A22BA /* libJailMonkey.a */,
);
name = Products;
sourceTree = "<group>";
};
7F906D7A1E6A1ADF00B8F49A /* Products */ = {
isa = PBXGroup;
children = (
@ -715,6 +787,9 @@
7DCC3D826CE640AF8F491692 /* BVLinearGradient.xcodeproj */,
5C3B95629BA74FB3A8377CB7 /* RCTOrientation.xcodeproj */,
85E82BCA0FD245EEA520D106 /* RNImagePicker.xcodeproj */,
D0281D64B98143668D6AD42B /* RNLocalAuth.xcodeproj */,
EBA6063A99C141098D40C67A /* RNPasscodeStatus.xcodeproj */,
27A6EA89298440439DA9F98D /* JailMonkey.xcodeproj */,
);
name = Libraries;
sourceTree = "<group>";
@ -831,6 +906,10 @@
ProductGroup = 37D8FEBE1E80B5230091F3BD /* Products */;
ProjectRef = 7DCC3D826CE640AF8F491692 /* BVLinearGradient.xcodeproj */;
},
{
ProductGroup = 7F8C4A5D1F3E21FB003A22BA /* Products */;
ProjectRef = 27A6EA89298440439DA9F98D /* JailMonkey.xcodeproj */;
},
{
ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */;
ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */;
@ -899,10 +978,18 @@
ProductGroup = 3779BE7C1EB1235400D081C1 /* Products */;
ProjectRef = 85E82BCA0FD245EEA520D106 /* RNImagePicker.xcodeproj */;
},
{
ProductGroup = 7F8C49621F3DFC30003A22BA /* Products */;
ProjectRef = D0281D64B98143668D6AD42B /* RNLocalAuth.xcodeproj */;
},
{
ProductGroup = 7F26919C1EE1DC51007574FE /* Products */;
ProjectRef = 7F26919B1EE1DC51007574FE /* RNNotifications.xcodeproj */;
},
{
ProductGroup = 7F8C49D11F3E070F003A22BA /* Products */;
ProjectRef = EBA6063A99C141098D40C67A /* RNPasscodeStatus.xcodeproj */;
},
{
ProductGroup = 7FDF28C41E1F4B1F00DBBE56 /* Products */;
ProjectRef = 2CBE9C0FB56E4FDA96C30792 /* RNSVG.xcodeproj */;
@ -1156,6 +1243,27 @@
remoteRef = 7F6877B11E7835E50094B63F /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F8C49871F3DFC30003A22BA /* libRNLocalAuth.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNLocalAuth.a;
remoteRef = 7F8C49861F3DFC30003A22BA /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F8C49F81F3E0710003A22BA /* libRNPasscodeStatus.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRNPasscodeStatus.a;
remoteRef = 7F8C49F71F3E0710003A22BA /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F8C4A621F3E21FB003A22BA /* libJailMonkey.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libJailMonkey.a;
remoteRef = 7F8C4A611F3E21FB003A22BA /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
7F906D971E6A1ADF00B8F49A /* libRNCookieManagerIOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@ -1266,6 +1374,9 @@
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
7F5053221F3F57AC00FE9812 /* BlurAppScreen.m in Sources */,
7F5052F61F3F3C1C00FE9812 /* UIImage+ImageEffects.m in Sources */,
7F1B28A31F3A76BB00701A6A /* MattermostManaged.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1291,7 +1402,12 @@
INFOPLIST_FILE = MattermostTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mattermost.app/Mattermost";
@ -1306,7 +1422,12 @@
INFOPLIST_FILE = MattermostTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mattermost.app/Mattermost";
@ -1323,6 +1444,7 @@
CURRENT_PROJECT_VERSION = 47;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS/**",
"$(SRCROOT)/../node_modules/react-native-orientation/iOS/RCTOrientation/**",
@ -1330,6 +1452,10 @@
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
"$(SRCROOT)/../node_modules/react-native-navigation/ios/**",
"$(SRCROOT)/../node_modules/react-native-notifications/RNNotifications/**",
"$(SRCROOT)/../node_modules/react-native-local-auth",
"$(SRCROOT)/../node_modules/react-native-passcode-status/ios",
"$(SRCROOT)/../node_modules/jail-monkey/JailMonkey",
"$(SRCROOT)/../node_modules/react-native/Libraries/**",
);
INFOPLIST_FILE = Mattermost/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@ -1356,6 +1482,7 @@
CURRENT_PROJECT_VERSION = 47;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS/**",
"$(SRCROOT)/../node_modules/react-native-orientation/iOS/RCTOrientation/**",
@ -1363,6 +1490,10 @@
"$(SRCROOT)/../node_modules/react-native-image-picker/ios",
"$(SRCROOT)/../node_modules/react-native-navigation/ios/**",
"$(SRCROOT)/../node_modules/react-native-notifications/RNNotifications/**",
"$(SRCROOT)/../node_modules/react-native-local-auth",
"$(SRCROOT)/../node_modules/react-native-passcode-status/ios",
"$(SRCROOT)/../node_modules/jail-monkey/JailMonkey",
"$(SRCROOT)/../node_modules/react-native/Libraries/**",
);
INFOPLIST_FILE = Mattermost/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";

View file

@ -13,6 +13,7 @@
#import "Orientation.h"
#import "RCCManager.h"
#import "RNNotifications.h"
#import "MattermostManaged.h"
@implementation AppDelegate

16
ios/MattermostManaged.h Normal file
View file

@ -0,0 +1,16 @@
//
// MattermostManaged.h
// Mattermost
//
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
//
#import <React/RCTEventEmitter.h>
@interface MattermostManaged : RCTEventEmitter
+ (void)sendConfigChangedEvent;
@end

99
ios/MattermostManaged.m Normal file
View file

@ -0,0 +1,99 @@
//
// MattermostManaged.m
// Mattermost
//
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
//
#import "RCTTextField.h"
#import "MattermostManaged.h"
@implementation MattermostManaged
RCT_EXPORT_MODULE();
-(void)startObserving {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(managedConfigDidChange:) name:@"managedConfigDidChange" object:nil];
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification
object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self remoteConfigChanged];
}];
}
- (void)stopObserving
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] removeObserver:NSUserDefaultsDidChangeNotification];
}
+ (void)sendConfigChangedEvent {
[[NSNotificationCenter defaultCenter] postNotificationName:@"managedConfigDidChange"
object:self
userInfo:nil];
}
- (NSArray<NSString *> *)supportedEvents
{
return @[@"managedConfigDidChange"];
}
// The Managed app configuration dictionary pushed down from an MDM server are stored in this key.
static NSString * const configurationKey = @"com.apple.configuration.managed";
// The dictionary that is sent back to the MDM server as feedback must be stored in this key.
static NSString * const feedbackKey = @"com.apple.feedback.managed";
- (void)managedConfigDidChange:(NSNotification *)notification
{
NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:configurationKey];
[self sendEventWithName:@"managedConfigDidChange" body:response];
}
- (void) remoteConfigChanged {
NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:configurationKey];
[self sendEventWithName:@"managedConfigDidChange" body:response];
}
RCT_EXPORT_METHOD(getConfig:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:configurationKey];
if (response) {
resolve(response);
}
else {
NSError *error = [NSError errorWithDomain:@"Mattermost Managed" code:-1 userInfo:nil];
reject(@"no managed configuration", @"The MDM vendor has not sent any Managed configuration", error);
}
}
RCT_EXPORT_METHOD(quitApp)
{
exit(0);
}
@end
@implementation RCTTextField (DisableCopyPaste)
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:configurationKey];
if(response) {
NSString *copyPasteProtection = response[@"copyAndPasteProtection"];
BOOL prevent = action == @selector(paste:) ||
action == @selector(copy:) ||
action == @selector(cut:) ||
action == @selector(_share:);
if ([copyPasteProtection isEqual: @"true"] && prevent) {
return NO;
}
}
return [super canPerformAction:action withSender:sender];
}
@end

112
ios/UIImage+ImageEffects.h Normal file
View file

@ -0,0 +1,112 @@
/*
File: UIImage+ImageEffects.h
Abstract: This is a category of UIImage that adds methods to apply blur and tint effects to an image. This is the code youll want to look out to find out how to use vImage to efficiently calculate a blur.
Version: 1.0
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2013 Apple Inc. All Rights Reserved.
Copyright © 2013 Apple Inc. All rights reserved.
WWDC 2013 License
NOTE: This Apple Software was supplied by Apple as part of a WWDC 2013
Session. Please refer to the applicable WWDC 2013 Session for further
information.
IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and
your use, installation, modification or redistribution of this Apple
software constitutes acceptance of these terms. If you do not agree with
these terms, please do not use, install, modify or redistribute this
Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple
Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES
NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
EA1002
5/3/2013
*/
@import UIKit;
@interface UIImage (ImageEffects)
- (UIImage *)applyLightEffect;
- (UIImage *)applyExtraLightEffect;
- (UIImage *)applyDarkEffect;
- (UIImage *)applyTintEffectWithColor:(UIColor *)tintColor;
- (UIImage *)applyBlurWithRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage;
- (UIImage *)rotateByDegrees:(CGFloat)degrees;
+ (UIImage *)imagePixelFromColor:(UIColor *)color;
+ (UIImage *)imageFromColor:(UIColor *)color withSize:(CGSize)size;
@end

310
ios/UIImage+ImageEffects.m Normal file
View file

@ -0,0 +1,310 @@
/*
File: UIImage+ImageEffects.m
Abstract: This is a category of UIImage that adds methods to apply blur and tint effects to an image. This is the code youll want to look out to find out how to use vImage to efficiently calculate a blur.
Version: 1.0
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2013 Apple Inc. All Rights Reserved.
Copyright © 2013 Apple Inc. All rights reserved.
WWDC 2013 License
NOTE: This Apple Software was supplied by Apple as part of a WWDC 2013
Session. Please refer to the applicable WWDC 2013 Session for further
information.
IMPORTANT: This Apple software is supplied to you by Apple Inc.
("Apple") in consideration of your agreement to the following terms, and
your use, installation, modification or redistribution of this Apple
software constitutes acceptance of these terms. If you do not agree with
these terms, please do not use, install, modify or redistribute this
Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a non-exclusive license, under
Apple's copyrights in this original Apple software (the "Apple
Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES
NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
EA1002
5/3/2013
*/
#import "UIImage+ImageEffects.h"
@import Accelerate;
#import <float.h>
@implementation UIImage (ImageEffects)
- (UIImage *)applyLightEffect
{
UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3];
return [self applyBlurWithRadius:30 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil];
}
- (UIImage *)applyExtraLightEffect
{
UIColor *tintColor = [UIColor colorWithWhite:0.97 alpha:0.82];
return [self applyBlurWithRadius:20 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil];
}
- (UIImage *)applyDarkEffect
{
UIColor *tintColor = [UIColor colorWithWhite:0.11 alpha:0.73];
return [self applyBlurWithRadius:20 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil];
}
- (UIImage *)applyTintEffectWithColor:(UIColor *)tintColor
{
const CGFloat EffectColorAlpha = 0.6;
UIColor *effectColor = tintColor;
int componentCount = CGColorGetNumberOfComponents(tintColor.CGColor);
if (componentCount == 2) {
CGFloat b;
if ([tintColor getWhite:&b alpha:NULL]) {
effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha];
}
}
else {
CGFloat r, g, b;
if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) {
effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha];
}
}
return [self applyBlurWithRadius:10 tintColor:effectColor saturationDeltaFactor:-1.0 maskImage:nil];
}
- (UIImage *)applyBlurWithRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage
{
// Check pre-conditions.
if (self.size.width < 1 || self.size.height < 1) {
NSLog (@"*** error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self);
return nil;
}
if (!self.CGImage) {
NSLog (@"*** error: image must be backed by a CGImage: %@", self);
return nil;
}
if (maskImage && !maskImage.CGImage) {
NSLog (@"*** error: maskImage must be backed by a CGImage: %@", maskImage);
return nil;
}
CGRect imageRect = { CGPointZero, self.size };
UIImage *effectImage = self;
BOOL hasBlur = blurRadius > __FLT_EPSILON__;
BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__;
if (hasBlur || hasSaturationChange) {
UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef effectInContext = UIGraphicsGetCurrentContext();
CGContextScaleCTM(effectInContext, 1.0, -1.0);
CGContextTranslateCTM(effectInContext, 0, -self.size.height);
CGContextDrawImage(effectInContext, imageRect, self.CGImage);
vImage_Buffer effectInBuffer;
effectInBuffer.data = CGBitmapContextGetData(effectInContext);
effectInBuffer.width = CGBitmapContextGetWidth(effectInContext);
effectInBuffer.height = CGBitmapContextGetHeight(effectInContext);
effectInBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectInContext);
UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef effectOutContext = UIGraphicsGetCurrentContext();
vImage_Buffer effectOutBuffer;
effectOutBuffer.data = CGBitmapContextGetData(effectOutContext);
effectOutBuffer.width = CGBitmapContextGetWidth(effectOutContext);
effectOutBuffer.height = CGBitmapContextGetHeight(effectOutContext);
effectOutBuffer.rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext);
if (hasBlur) {
// A description of how to compute the box kernel width from the Gaussian
// radius (aka standard deviation) appears in the SVG spec:
// http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
//
// For larger values of 's' (s >= 2.0), an approximation can be used: Three
// successive box-blurs build a piece-wise quadratic convolution kernel, which
// approximates the Gaussian kernel to within roughly 3%.
//
// let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
//
// ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
//
CGFloat inputRadius = blurRadius * [[UIScreen mainScreen] scale];
NSUInteger radius = floor(inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5);
if (radius % 2 != 1) {
radius += 1; // force radius to be odd so that the three box-blur methodology works.
}
vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, radius, radius, 0, kvImageEdgeExtend);
}
BOOL effectImageBuffersAreSwapped = NO;
if (hasSaturationChange) {
CGFloat s = saturationDeltaFactor;
CGFloat floatingPointSaturationMatrix[] = {
0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0,
0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0,
0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0,
0, 0, 0, 1,
};
const int32_t divisor = 256;
NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix)/sizeof(floatingPointSaturationMatrix[0]);
int16_t saturationMatrix[matrixSize];
for (NSUInteger i = 0; i < matrixSize; ++i) {
saturationMatrix[i] = (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor);
}
if (hasBlur) {
vImageMatrixMultiply_ARGB8888(&effectOutBuffer, &effectInBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags);
effectImageBuffersAreSwapped = YES;
}
else {
vImageMatrixMultiply_ARGB8888(&effectInBuffer, &effectOutBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags);
}
}
if (!effectImageBuffersAreSwapped)
effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (effectImageBuffersAreSwapped)
effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
// Set up output context.
UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef outputContext = UIGraphicsGetCurrentContext();
CGContextScaleCTM(outputContext, 1.0, -1.0);
CGContextTranslateCTM(outputContext, 0, -self.size.height);
// Draw base image.
CGContextDrawImage(outputContext, imageRect, self.CGImage);
// Draw effect image.
if (hasBlur) {
CGContextSaveGState(outputContext);
if (maskImage) {
CGContextClipToMask(outputContext, imageRect, maskImage.CGImage);
}
CGContextDrawImage(outputContext, imageRect, effectImage.CGImage);
CGContextRestoreGState(outputContext);
}
// Add in color tint.
if (tintColor) {
CGContextSaveGState(outputContext);
CGContextSetFillColorWithColor(outputContext, tintColor.CGColor);
CGContextFillRect(outputContext, imageRect);
CGContextRestoreGState(outputContext);
}
// Output image is ready.
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
- (UIImage *)rotateByDegrees:(CGFloat)degrees {
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.size.width, self.size.height)];
CGAffineTransform t = CGAffineTransformMakeRotation(degrees * M_PI / 180.0f);
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
CGContextRotateCTM(bitmap, degrees * M_PI / 180.0f);
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(-self.size.width / 2, -self.size.height / 2, self.size.width, self.size.height), self.CGImage);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
+ (UIImage *)imagePixelFromColor:(UIColor *)color {
return [UIImage imageFromColor:color withSize:CGSizeMake(1.0f, 1.0f)];
}
+ (UIImage *)imageFromColor:(UIColor *)color withSize:(CGSize)size {
CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end

View file

@ -13,6 +13,7 @@
"commonmark-react-renderer": "hmhealey/commonmark-react-renderer#c5d00343664c89da40d5a2ffa8b083e7cc1615d7",
"deep-equal": "1.0.1",
"intl": "1.2.5",
"jail-monkey": "0.0.8",
"mattermost-redux": "mattermost/mattermost-redux#master",
"prop-types": "15.5.10",
"react": "16.0.0-alpha.6",
@ -22,15 +23,17 @@
"react-native-bottom-sheet": "1.0.1",
"react-native-button": "1.8.2",
"react-native-cookies": "3.1.0",
"react-native-device-info": "0.10.2",
"react-native-device-info": "0.11.0",
"react-native-drawer": "2.3.0",
"react-native-exception-handler": "1.1.0",
"react-native-image-picker": "jp928/react-native-image-picker#6ee35b69f3dbd6c7c66f580fd4d9eabf398703d4",
"react-native-keyboard-aware-scroll-view": "0.2.8",
"react-native-linear-gradient": "2.0.0",
"react-native-local-auth": "enahum/react-native-local-auth.git",
"react-native-navigation": "1.1.131",
"react-native-notifications": "enahum/react-native-notifications.git",
"react-native-orientation": "enahum/react-native-orientation.git",
"react-native-passcode-status": "1.1.0",
"react-native-status-bar-size": "0.3.2",
"react-native-svg": "5.1.8",
"react-native-swiper": "1.5.4",

View file

@ -1519,9 +1519,9 @@ commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
commonmark-react-renderer@hmhealey/commonmark-react-renderer#11bf3a089900eda6f3e951f8b8c80663cff4d6a3:
version "4.3.2"
resolved "https://codeload.github.com/hmhealey/commonmark-react-renderer/tar.gz/11bf3a089900eda6f3e951f8b8c80663cff4d6a3"
commonmark-react-renderer@hmhealey/commonmark-react-renderer#c5d00343664c89da40d5a2ffa8b083e7cc1615d7:
version "4.3.3"
resolved "https://codeload.github.com/hmhealey/commonmark-react-renderer/tar.gz/c5d00343664c89da40d5a2ffa8b083e7cc1615d7"
dependencies:
in-publish "^2.0.0"
lodash.assign "^4.2.0"
@ -1530,7 +1530,7 @@ commonmark-react-renderer@hmhealey/commonmark-react-renderer#11bf3a089900eda6f3e
xss-filters "^1.2.6"
commonmark@hmhealey/commonmark.js#9f33c90b5c82adfef30b87c7cd15aea2e2764b51:
version "0.27.0"
version "0.28.0"
resolved "https://codeload.github.com/hmhealey/commonmark.js/tar.gz/9f33c90b5c82adfef30b87c7cd15aea2e2764b51"
dependencies:
entities "~ 1.1.1"
@ -3208,6 +3208,10 @@ istanbul-reports@^1.1.0:
dependencies:
handlebars "^4.0.3"
jail-monkey@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/jail-monkey/-/jail-monkey-0.0.8.tgz#076dfae94b4648950b825ecc2d33aac0642d97b6"
jest-haste-map@19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-19.0.0.tgz#adde00b62b1fe04432a104b3254fc5004514b55e"
@ -3652,7 +3656,7 @@ makeerror@1.0.x:
mattermost-redux@mattermost/mattermost-redux#master:
version "0.0.1"
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/e5297912f528b822b61c61a36735fb4d6699d08d"
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/3bab6079caf711debd30eed5fc5283b6f97c1aeb"
dependencies:
deep-equal "1.0.1"
harmony-reflect "1.5.1"
@ -4477,9 +4481,9 @@ react-native-cookies@3.1.0:
dependencies:
invariant "^2.1.0"
react-native-device-info@0.10.2:
version "0.10.2"
resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-0.10.2.tgz#350cd68ed43839022ddea0a3a423439f16fa4fba"
react-native-device-info@0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-0.11.0.tgz#e96dd0e3f8a7d7e3022c1f441f51fa912fdc7444"
react-native-drawer@2.3.0:
version "2.3.0"
@ -4505,6 +4509,10 @@ react-native-linear-gradient@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.0.0.tgz#7442c00e1fcd9fca329119dcbaf744d12bcd1b5a"
react-native-local-auth@enahum/react-native-local-auth.git:
version "1.4.1"
resolved "https://codeload.github.com/enahum/react-native-local-auth/tar.gz/ee2e53bcfe3fd0c49c28ff48f05cc19d5b56668c"
react-native-mock@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/react-native-mock/-/react-native-mock-0.3.1.tgz#13d22433c5351a8a7fb8aedd7862b614d087851c"
@ -4556,6 +4564,10 @@ react-native-orientation@enahum/react-native-orientation.git:
version "1.17.0"
resolved "https://codeload.github.com/enahum/react-native-orientation/tar.gz/8b8495c91d8e63598874a141279cf5409e26ad3a"
react-native-passcode-status@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/react-native-passcode-status/-/react-native-passcode-status-1.1.0.tgz#e5922d5f4d79bc09849438d620406e5ccd31168a"
react-native-status-bar-size@0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/react-native-status-bar-size/-/react-native-status-bar-size-0.3.2.tgz#e3ffd906f021220c256857a8de6b945f8ea3fabc"