PLT-5630 Implement push notifications and unread badge on app icon (#338)
* enable ios push notifications * enable android push notifications * NOTICE for push notification and message bar * include message in noty * re-wording notices * feedback review
This commit is contained in:
parent
e17eb6c237
commit
6324f22095
37 changed files with 600 additions and 68 deletions
66
NOTICE.txt
66
NOTICE.txt
|
|
@ -735,3 +735,69 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-push-notification
|
||||
|
||||
This product contains 'react-native-push-notification', a library to provide React Native local and remote notifications for iOS and Android.
|
||||
|
||||
* HOMEPAGE
|
||||
* https://github.com/zo0r/react-native-push-notification
|
||||
|
||||
* LICENSE:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Dieam
|
||||
|
||||
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-message-bar
|
||||
|
||||
This product contains 'react-native-message-bar', a library for displaying a message bar notification component at the top of the screen for React Native (Android and iOS) projects.
|
||||
|
||||
* HOMEPAGE
|
||||
* https://github.com/KBLNY/react-native-message-bar
|
||||
|
||||
* LICENSE:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 KBLNY
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -143,6 +143,10 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':react-native-push-notification')
|
||||
compile ('com.google.android.gms:play-services-gcm:9.0.0') {
|
||||
force = true;
|
||||
}
|
||||
compile project(':react-native-cookies')
|
||||
compile project(':react-native-vector-icons')
|
||||
compile project(':react-native-svg')
|
||||
|
|
|
|||
42
android/app/google-services.json
Normal file
42
android/app/google-services.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"project_info": {
|
||||
"project_number": "184930218130",
|
||||
"firebase_url": "https://api-7231322553409637977-752355.firebaseio.com",
|
||||
"project_id": "api-7231322553409637977-752355",
|
||||
"storage_bucket": "api-7231322553409637977-752355.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:184930218130:android:a0e553d1a1f043b5",
|
||||
"android_client_info": {
|
||||
"package_name": "com.mattermost.react.native"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "184930218130-8nahrspll1opff0uogtkh2qsv8coqngo.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCkZkU2ECVg-mmdCG5OoHHQXtKiENuvWPE"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"analytics_service": {
|
||||
"status": 1
|
||||
},
|
||||
"appinvite_service": {
|
||||
"status": 1,
|
||||
"other_platform_oauth_client": []
|
||||
},
|
||||
"ads_service": {
|
||||
"status": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
|
|
@ -5,6 +5,13 @@
|
|||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<permission
|
||||
android:name="${applicationId}.permission.C2D_MESSAGE"
|
||||
android:protectionLevel="signature" />
|
||||
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="16"
|
||||
|
|
@ -27,6 +34,30 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
<receiver
|
||||
android:name="com.google.android.gms.gcm.GcmReceiver"
|
||||
android:exported="true"
|
||||
android:permission="com.google.android.c2dm.permission.SEND" >
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
<category android:name="${applicationId}" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
|
||||
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<service android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationRegistrationService"/>
|
||||
<service
|
||||
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
|
||||
android:exported="false" >
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.util.Log;
|
|||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.psykar.cookiemanager.CookieManagerPackage;
|
||||
import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage;
|
||||
import com.oblador.vectoricons.VectorIconsPackage;
|
||||
import com.horcrux.svg.RNSvgPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
|
|
@ -29,6 +30,7 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new CookieManagerPackage(),
|
||||
new ReactNativePushNotificationPackage(),
|
||||
new VectorIconsPackage(),
|
||||
new RNSvgPackage()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ buildscript {
|
|||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.3.1'
|
||||
classpath 'com.google.gms:google-services:3.0.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
rootProject.name = 'Mattermost'
|
||||
include ':react-native-cookies'
|
||||
project(':react-native-cookies').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-cookies/android')
|
||||
include ':react-native-push-notification'
|
||||
project(':react-native-push-notification').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-push-notification/android')
|
||||
include ':react-native-vector-icons'
|
||||
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
||||
|
||||
|
|
|
|||
|
|
@ -19,11 +19,17 @@ export function loadStorage() {
|
|||
|
||||
const actions = [
|
||||
{type: GeneralTypes.RECEIVED_APP_CREDENTIALS, data: credentials},
|
||||
{type: GeneralTypes.RECEIVED_SERVER_VERSION, data: serverVersion},
|
||||
{type: TeamsTypes.SELECT_TEAM, data: (currentTeamId || '')},
|
||||
{type: ChannelTypes.SELECT_CHANNEL, data: currentChannelId}
|
||||
{type: GeneralTypes.RECEIVED_SERVER_VERSION, data: serverVersion}
|
||||
];
|
||||
|
||||
if (currentTeamId) {
|
||||
actions.push({type: TeamsTypes.SELECT_TEAM, data: currentTeamId});
|
||||
}
|
||||
|
||||
if (currentChannelId) {
|
||||
actions.push({type: ChannelTypes.SELECT_CHANNEL, data: currentChannelId});
|
||||
}
|
||||
|
||||
// Load post drafts if there are any
|
||||
if (otherStorage.postDrafts) {
|
||||
Object.keys(otherStorage.postDrafts).forEach((d) => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
import {NavigationTypes, ViewTypes} from 'app/constants';
|
||||
import Routes from 'app/navigation/routes';
|
||||
import {
|
||||
handleSelectChannel,
|
||||
loadChannelsIfNecessary,
|
||||
loadProfilesAndTeamMembersForDMSidebar
|
||||
} from 'app/actions/views/channel';
|
||||
import {goToChannelView} from 'app/actions/views/load_team';
|
||||
import {handleTeamChange, selectFirstAvailableTeam} from 'app/actions/views/select_team';
|
||||
import {updateStorage} from 'app/actions/storage';
|
||||
|
||||
import Client from 'service/client';
|
||||
import {markChannelAsRead, viewChannel} from 'service/actions/channels';
|
||||
import {getClientConfig, getLicenseConfig, setServerVersion} from 'service/actions/general';
|
||||
import {loadMe} from 'service/actions/users';
|
||||
|
||||
|
|
@ -37,8 +45,45 @@ export function loadConfigAndLicense(serverVersion) {
|
|||
};
|
||||
}
|
||||
|
||||
export function queueNotification(notification) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({type: ViewTypes.NOTIFICATION_CHANGED, data: notification}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function clearNotification() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({type: ViewTypes.NOTIFICATION_CHANGED, data: null}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToNotification(notification) {
|
||||
return async (dispatch, getState) => {
|
||||
const {data} = notification;
|
||||
let teamId = data.team_id;
|
||||
if (teamId) {
|
||||
const {teams} = getState().entities.teams;
|
||||
await handleTeamChange(teams[teamId])(dispatch, getState);
|
||||
loadChannelsIfNecessary(teamId)(dispatch, getState);
|
||||
} else {
|
||||
await selectFirstAvailableTeam()(dispatch, getState);
|
||||
teamId = getState().entities.teams.currentId;
|
||||
}
|
||||
|
||||
const channelId = data.channel_id;
|
||||
viewChannel(teamId, channelId)(dispatch, getState);
|
||||
loadProfilesAndTeamMembersForDMSidebar(teamId)(dispatch, getState);
|
||||
await handleSelectChannel(channelId)(dispatch, getState);
|
||||
goToChannelView()(dispatch, getState);
|
||||
markChannelAsRead(teamId, channelId)(dispatch, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
goToSelectServer,
|
||||
loadConfigAndLicense,
|
||||
setStoreFromLocalData
|
||||
setStoreFromLocalData,
|
||||
queueNotification,
|
||||
clearNotification,
|
||||
goToNotification
|
||||
};
|
||||
|
|
|
|||
35
app/components/push_notification/index.js
Normal file
35
app/components/push_notification/index.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {goToNotification, queueNotification} from 'app/actions/views/root';
|
||||
import {setDeviceToken} from 'service/actions/general';
|
||||
import {getUnreads} from 'service/selectors/entities/channels';
|
||||
|
||||
import PushNotification from './push_notification';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {currentId: currentTeamId} = state.entities.teams;
|
||||
const {currentId: currentChannelId} = state.entities.channels;
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
currentTeamId,
|
||||
currentChannelId,
|
||||
...getUnreads(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToNotification,
|
||||
queueNotification,
|
||||
setDeviceToken
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PushNotification);
|
||||
123
app/components/push_notification/push_notification.js
Normal file
123
app/components/push_notification/push_notification.js
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {Platform} from 'react-native';
|
||||
import DeviceNotification from 'react-native-push-notification';
|
||||
import {MessageBar, MessageBarManager} from 'react-native-message-bar';
|
||||
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
import icon from 'assets/images/icon.png';
|
||||
import {GooglePlaySenderId} from 'assets/config.json';
|
||||
|
||||
import {Constants} from 'service/constants';
|
||||
|
||||
export default class PushNotification extends PureComponent {
|
||||
static propTypes = {
|
||||
currentTeamId: PropTypes.string,
|
||||
currentChannelId: PropTypes.string,
|
||||
actions: PropTypes.shape({
|
||||
goToNotification: PropTypes.func.isRequired,
|
||||
queueNotification: PropTypes.func.isRequired,
|
||||
setDeviceToken: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.isConfigured = false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
MessageBarManager.registerMessageBar(this.refs.notification);
|
||||
this.configurePushNotifications();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
MessageBarManager.unregisterMessageBar();
|
||||
DeviceNotification.unregister();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
if (this.isConfigured) {
|
||||
DeviceNotification.setApplicationIconBadgeNumber(props.mentionCount || 0);
|
||||
}
|
||||
}
|
||||
|
||||
configurePushNotifications = () => {
|
||||
DeviceNotification.configure({
|
||||
onRegister: this.onRegisterDevice,
|
||||
onNotification: this.onPushNotification,
|
||||
senderID: GooglePlaySenderId,
|
||||
popInitialNotification: true,
|
||||
requestPermissions: true
|
||||
});
|
||||
};
|
||||
|
||||
onRegisterDevice = (data) => {
|
||||
const prefix = Platform.OS === 'ios' ? Constants.PUSH_NOTIFY_APPLE_REACT_NATIVE : Constants.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
|
||||
this.props.actions.setDeviceToken(`${prefix}:${data.token}`);
|
||||
this.isConfigured = true;
|
||||
};
|
||||
|
||||
onPushNotification = (deviceNotification) => {
|
||||
const {foreground, userInteraction, data, message} = deviceNotification;
|
||||
let notification;
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
notification = {
|
||||
data: {
|
||||
channel_id: deviceNotification.channel_id,
|
||||
team_id: deviceNotification.team_id
|
||||
},
|
||||
message
|
||||
};
|
||||
} else {
|
||||
notification = {
|
||||
data,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
if (foreground) {
|
||||
this.handleInAppNotification(notification);
|
||||
} else if (userInteraction) {
|
||||
this.onNotificationTapped(notification);
|
||||
}
|
||||
};
|
||||
|
||||
onNotificationTapped = (notification) => {
|
||||
const {currentTeamId, currentChannelId} = this.props;
|
||||
|
||||
if (currentTeamId && currentChannelId) {
|
||||
// this means that the store has the necessary data
|
||||
this.props.actions.goToNotification(notification);
|
||||
} else {
|
||||
this.props.actions.queueNotification(notification);
|
||||
}
|
||||
};
|
||||
|
||||
handleInAppNotification = (notification) => {
|
||||
const {message} = notification;
|
||||
|
||||
MessageBarManager.showAlert({
|
||||
alertType: 'info',
|
||||
avatar: icon,
|
||||
avatarStyle: {borderRadius: 10, width: 20, height: 20},
|
||||
message,
|
||||
stylesheetInfo: {backgroundColor: changeOpacity('#000', 0.9)},
|
||||
messageStyle: {color: 'white', fontSize: 13},
|
||||
viewTopInset: 15,
|
||||
viewBottomInset: 15,
|
||||
duration: 5000,
|
||||
onTapped: () => this.onNotificationTapped(notification)
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MessageBar ref='notification'/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,25 @@
|
|||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {AppState} from 'react-native';
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {AppState, View} from 'react-native';
|
||||
import {IntlProvider} from 'react-intl';
|
||||
import PushNotification from 'app/components/push_notification';
|
||||
|
||||
import {Constants} from 'service/constants';
|
||||
import {getTranslations} from 'service/i18n';
|
||||
import EventEmitter from 'service/utils/event_emitter';
|
||||
|
||||
export default class Root extends React.Component {
|
||||
export default class Root extends PureComponent {
|
||||
static propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
locale: React.PropTypes.string.isRequired,
|
||||
actions: React.PropTypes.shape({
|
||||
loadConfigAndLicense: React.PropTypes.func.isRequired,
|
||||
setAppState: React.PropTypes.func.isRequired
|
||||
children: PropTypes.node,
|
||||
currentTeamId: PropTypes.string,
|
||||
currentChannelId: PropTypes.string,
|
||||
locale: PropTypes.string.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
loadConfigAndLicense: PropTypes.func.isRequired,
|
||||
setAppState: PropTypes.func.isRequired,
|
||||
flushToStorage: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
|
|
@ -38,6 +43,10 @@ export default class Root extends React.Component {
|
|||
|
||||
handleAppStateChange(appState) {
|
||||
this.props.actions.setAppState(appState === 'active');
|
||||
|
||||
if (appState === 'inactive') {
|
||||
this.props.actions.flushToStorage();
|
||||
}
|
||||
}
|
||||
|
||||
handleConfigChanged = (serverVersion) => {
|
||||
|
|
@ -52,7 +61,10 @@ export default class Root extends React.Component {
|
|||
locale={locale}
|
||||
messages={getTranslations(locale)}
|
||||
>
|
||||
{this.props.children}
|
||||
<View style={{flex: 1}}>
|
||||
{this.props.children}
|
||||
<PushNotification/>
|
||||
</View>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,17 @@ import {connect} from 'react-redux';
|
|||
|
||||
import Config from 'assets/config.json';
|
||||
|
||||
import {loadConfigAndLicense} from 'app/actions/views/root';
|
||||
import {setAppState} from 'service/actions/general';
|
||||
import {flushToStorage} from 'app/actions/storage';
|
||||
import {goToNotification, loadConfigAndLicense, queueNotification} from 'app/actions/views/root';
|
||||
import {setAppState, setDeviceToken} from 'service/actions/general';
|
||||
|
||||
import Root from './root';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const users = state.entities.users;
|
||||
const currentUserId = users.currentId;
|
||||
const {currentId: currentTeamId} = state.entities.teams;
|
||||
const {currentId: currentChannelId} = state.entities.channels;
|
||||
|
||||
let locale = Config.DefaultLocale;
|
||||
if (currentUserId && users.profiles[currentUserId]) {
|
||||
|
|
@ -22,6 +25,8 @@ function mapStateToProps(state, ownProps) {
|
|||
|
||||
return {
|
||||
...ownProps,
|
||||
currentTeamId,
|
||||
currentChannelId,
|
||||
locale
|
||||
};
|
||||
}
|
||||
|
|
@ -30,7 +35,11 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
loadConfigAndLicense,
|
||||
setAppState
|
||||
goToNotification,
|
||||
queueNotification,
|
||||
setAppState,
|
||||
setDeviceToken,
|
||||
flushToStorage
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ const ViewTypes = keyMirror({
|
|||
POST_DRAFT_CHANGED: null,
|
||||
COMMENT_DRAFT_CHANGED: null,
|
||||
|
||||
OPTIONS_MODAL_CHANGED: null
|
||||
OPTIONS_MODAL_CHANGED: null,
|
||||
|
||||
NOTIFICATION_CHANGED: null
|
||||
});
|
||||
|
||||
export default ViewTypes;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ const state = {
|
|||
appState: false,
|
||||
credentials: {},
|
||||
config: {},
|
||||
license: {}
|
||||
license: {},
|
||||
deviceToken: ''
|
||||
},
|
||||
users: {
|
||||
currentId: '',
|
||||
|
|
@ -281,6 +282,7 @@ const state = {
|
|||
loginId: '',
|
||||
password: ''
|
||||
},
|
||||
notification: null,
|
||||
optionsModal: {
|
||||
title: '',
|
||||
options: [],
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {combineReducers} from 'redux';
|
|||
import channel from './channel';
|
||||
import i18n from './i18n';
|
||||
import login from './login';
|
||||
import notification from './notification';
|
||||
import optionsModal from './options_modal';
|
||||
import selectServer from './select_server';
|
||||
import thread from './thread';
|
||||
|
|
@ -14,6 +15,7 @@ export default combineReducers({
|
|||
channel,
|
||||
i18n,
|
||||
login,
|
||||
notification,
|
||||
optionsModal,
|
||||
selectServer,
|
||||
thread
|
||||
|
|
|
|||
13
app/reducers/views/notification.js
Normal file
13
app/reducers/views/notification.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
export default function notification(state = null, action) {
|
||||
switch (action.type) {
|
||||
case ViewTypes.NOTIFICATION_CHANGED:
|
||||
return action.data;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
@ -62,9 +62,11 @@ export default class Channel extends React.PureComponent {
|
|||
this.props.subscribeToHeaderEvent('open_right_menu', this.openRightMenuDrawer);
|
||||
this.props.subscribeToHeaderEvent('show_channel_info', this.props.actions.goToChannelInfo);
|
||||
EventEmitter.on('leave_team', this.handleLeaveTeam);
|
||||
const teamId = this.props.currentTeam.id;
|
||||
this.props.actions.initWebSocket(Platform.OS);
|
||||
this.loadChannels(teamId);
|
||||
if (this.props.currentTeam) {
|
||||
const teamId = this.props.currentTeam.id;
|
||||
this.loadChannels(teamId);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -72,7 +74,7 @@ export default class Channel extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.currentTeam && this.props.currentTeam.id !== nextProps.currentTeam.id) {
|
||||
if (this.props.currentTeam && nextProps.currentTeam && this.props.currentTeam.id !== nextProps.currentTeam.id) {
|
||||
const teamId = nextProps.currentTeam.id;
|
||||
this.loadChannels(teamId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {getTheme} from 'service/selectors/entities/preferences';
|
|||
function ChannelDrawerButton(props) {
|
||||
let badge;
|
||||
let badgeCount = props.mentionCount;
|
||||
|
||||
if (!badgeCount && props.messageCount) {
|
||||
badgeCount = -1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,35 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
|
||||
import Loading from 'app/components/loading';
|
||||
|
||||
import {RequestStatus} from 'service/constants';
|
||||
|
||||
export default class LoadTeam extends React.Component {
|
||||
export default class LoadTeam extends PureComponent {
|
||||
static propTypes = {
|
||||
teams: React.PropTypes.object.isRequired,
|
||||
myMembers: React.PropTypes.object.isRequired,
|
||||
teamsRequest: React.PropTypes.object.isRequired,
|
||||
currentTeam: React.PropTypes.object,
|
||||
actions: React.PropTypes.shape({
|
||||
goToChannelView: React.PropTypes.func.isRequired,
|
||||
handleTeamChange: React.PropTypes.func.isRequired
|
||||
notification: PropTypes.object,
|
||||
teams: PropTypes.object.isRequired,
|
||||
myMembers: PropTypes.object.isRequired,
|
||||
teamsRequest: PropTypes.object.isRequired,
|
||||
currentTeam: PropTypes.object,
|
||||
actions: PropTypes.shape({
|
||||
clearNotification: PropTypes.func.isRequired,
|
||||
goToChannelView: PropTypes.func.isRequired,
|
||||
goToNotification: PropTypes.func.isRequired,
|
||||
handleTeamChange: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const {currentTeam, myMembers, teams} = this.props;
|
||||
const {notification, currentTeam, myMembers, teams} = this.props;
|
||||
const {clearNotification, goToNotification} = this.props.actions;
|
||||
|
||||
if (currentTeam) {
|
||||
if (notification) {
|
||||
clearNotification();
|
||||
goToNotification(notification);
|
||||
} else if (currentTeam) {
|
||||
this.onSelectTeam(currentTeam);
|
||||
} else if (!currentTeam) {
|
||||
this.selectFirstTeam(teams, myMembers);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {bindActionCreators} from 'redux';
|
|||
import {connect} from 'react-redux';
|
||||
|
||||
import {goToChannelView} from 'app/actions/views/load_team';
|
||||
import {clearNotification, goToNotification} from 'app/actions/views/root';
|
||||
import {handleTeamChange} from 'app/actions/views/select_team';
|
||||
import {getCurrentTeam} from 'service/selectors/entities/teams';
|
||||
|
||||
|
|
@ -16,14 +17,17 @@ function mapStateToProps(state) {
|
|||
teamsRequest: state.requests.teams.allTeams,
|
||||
teams: state.entities.teams.teams,
|
||||
currentTeam: getCurrentTeam(state),
|
||||
myMembers: state.entities.teams.myMembers
|
||||
myMembers: state.entities.teams.myMembers,
|
||||
notification: state.views.notification
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
clearNotification,
|
||||
goToChannelView,
|
||||
goToNotification,
|
||||
handleTeamChange
|
||||
}, dispatch)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {AppState, AsyncStorage} from 'react-native';
|
||||
import {AsyncStorage} from 'react-native';
|
||||
import Loading from 'app/components/loading';
|
||||
|
||||
import {RequestStatus} from 'service/constants';
|
||||
|
|
@ -10,16 +10,15 @@ import {RequestStatus} from 'service/constants';
|
|||
export default class Root extends PureComponent {
|
||||
static propTypes = {
|
||||
credentials: PropTypes.object,
|
||||
logoutRequest: PropTypes.object.isRequired,
|
||||
loginRequest: PropTypes.object.isRequired,
|
||||
logoutRequest: PropTypes.object,
|
||||
loginRequest: PropTypes.object,
|
||||
actions: PropTypes.shape({
|
||||
goToLoadTeam: PropTypes.func.isRequired,
|
||||
goToSelectServer: PropTypes.func.isRequired,
|
||||
goToLoadTeam: PropTypes.func,
|
||||
goToSelectServer: PropTypes.func,
|
||||
handleServerUrlChanged: PropTypes.func.isRequired,
|
||||
flushToStorage: PropTypes.func.isRequired,
|
||||
loadStorage: PropTypes.func.isRequired,
|
||||
removeStorage: PropTypes.func.isRequired,
|
||||
setStoreFromLocalData: PropTypes.func.isRequired
|
||||
loadStorage: PropTypes.func,
|
||||
removeStorage: PropTypes.func,
|
||||
setStoreFromLocalData: PropTypes.func
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
|
|
@ -30,7 +29,6 @@ export default class Root extends PureComponent {
|
|||
componentDidMount() {
|
||||
// Any initialization logic for navigation, setting up the client, etc should go here
|
||||
this.init();
|
||||
AppState.addEventListener('change', this.handleAppStateChange);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
|
|
@ -43,13 +41,6 @@ export default class Root extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleAppStateChange = (event) => {
|
||||
// App gets killed, phone call comes in, app is pushed to the background, etc...
|
||||
if (event === 'inactive') {
|
||||
this.props.actions.flushToStorage();
|
||||
}
|
||||
};
|
||||
|
||||
init = () => {
|
||||
if (this.props.logoutRequest.status === RequestStatus.SUCCESS) {
|
||||
this.props.actions.removeStorage().then(() => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux';
|
|||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {flushToStorage, loadStorage, removeStorage} from 'app/actions/storage';
|
||||
import {loadStorage, removeStorage} from 'app/actions/storage';
|
||||
import {goToSelectServer, setStoreFromLocalData} from 'app/actions/views/root';
|
||||
import {handleServerUrlChanged} from 'app/actions/views/select_server';
|
||||
import {goToLoadTeam} from 'app/actions/navigation';
|
||||
|
|
@ -27,7 +27,6 @@ function mapDispatchToProps(dispatch) {
|
|||
goToLoadTeam,
|
||||
goToSelectServer,
|
||||
handleServerUrlChanged,
|
||||
flushToStorage,
|
||||
loadStorage,
|
||||
removeStorage,
|
||||
setStoreFromLocalData
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
"TestServerUrl": "http://localhost:8065",
|
||||
"DefaultLocale": "en",
|
||||
"DefaultTheme": "default",
|
||||
"ShowErrorsList": false
|
||||
"ShowErrorsList": false,
|
||||
"GooglePlaySenderId": "184930218130"
|
||||
}
|
||||
|
|
|
|||
BIN
assets/base/images/icon.png
Normal file
BIN
assets/base/images/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 462 KiB |
|
|
@ -29,6 +29,7 @@
|
|||
420A7328E12C4B72AEF420CE /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EDC04CBCF81642219D199CBB /* Octicons.ttf */; };
|
||||
5E1AF7B72B8D4A4E9E53FF9D /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 005346E5C0E542BFABAE1411 /* FontAwesome.ttf */; };
|
||||
5E9157361DD0AC6A00FF2AA8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
|
||||
7F63D2841E6C9585001FAE12 /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F63D2811E6C957C001FAE12 /* libRCTPushNotification.a */; };
|
||||
7FBB5E9A1E1F5A4B000DE18A /* libRNSVG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF28E11E1F4B1F00DBBE56 /* libRNSVG.a */; };
|
||||
7FBB5E9B1E1F5A4B000DE18A /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF290C1E1F4B4E00DBBE56 /* libRNVectorIcons.a */; };
|
||||
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
|
||||
|
|
@ -230,6 +231,20 @@
|
|||
remoteGlobalIDString = 1BD725DA1CF77A8B005DBD79;
|
||||
remoteInfo = RNCookieManagerIOS;
|
||||
};
|
||||
7F63D2801E6C957C001FAE12 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 134814201AA4EA6300B7C361;
|
||||
remoteInfo = RCTPushNotification;
|
||||
};
|
||||
7F63D2821E6C957C001FAE12 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 3D05745F1DE6004600184BB4;
|
||||
remoteInfo = "RCTPushNotification-tvOS";
|
||||
};
|
||||
7FDF28E01E1F4B1F00DBBE56 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 2CBE9C0FB56E4FDA96C30792 /* RNSVG.xcodeproj */;
|
||||
|
|
@ -285,6 +300,8 @@
|
|||
59954D479A89488091EB588F /* RNSearchBar.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSearchBar.xcodeproj; path = "../node_modules/react-native-search-bar/RNSearchBar.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>"; };
|
||||
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; 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>"; };
|
||||
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
|
||||
AC6EB561E1F64C17A69D2FAD /* Entypo.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Entypo.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = "<group>"; };
|
||||
CE45EB909FB44F3597EF3C60 /* libRNCookieManagerIOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCookieManagerIOS.a; sourceTree = "<group>"; };
|
||||
|
|
@ -319,6 +336,7 @@
|
|||
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */,
|
||||
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */,
|
||||
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */,
|
||||
7F63D2841E6C9585001FAE12 /* libRCTPushNotification.a in Frameworks */,
|
||||
B50561A109B4442299F82327 /* libRNSearchBar.a in Frameworks */,
|
||||
9906AC672C594EEB9D5EE503 /* libRNCookieManagerIOS.a in Frameworks */,
|
||||
);
|
||||
|
|
@ -424,6 +442,7 @@
|
|||
13B07FAE1A68108700A75B9A /* Mattermost */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */,
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
||||
13B07FB01A68108700A75B9A /* AppDelegate.m */,
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||
|
|
@ -483,6 +502,15 @@
|
|||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7F63D27C1E6C957C001FAE12 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F63D2811E6C957C001FAE12 /* libRCTPushNotification.a */,
|
||||
7F63D2831E6C957C001FAE12 /* libRCTPushNotification-tvOS.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7FDF28C41E1F4B1F00DBBE56 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -502,6 +530,7 @@
|
|||
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */,
|
||||
5E91572D1DD0AC6500FF2AA8 /* RCTAnimation.xcodeproj */,
|
||||
146833FF1AC3E56700842450 /* React.xcodeproj */,
|
||||
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */,
|
||||
|
|
@ -607,6 +636,11 @@
|
|||
13B07F861A680F5B00A75B9A = {
|
||||
DevelopmentTeam = UQ8HT4Q2XM;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.Push = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -646,6 +680,10 @@
|
|||
ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */;
|
||||
ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 7F63D27C1E6C957C001FAE12 /* Products */;
|
||||
ProjectRef = 7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */;
|
||||
ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */;
|
||||
|
|
@ -874,6 +912,20 @@
|
|||
remoteRef = 7F906D961E6A1ADF00B8F49A /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
7F63D2811E6C957C001FAE12 /* libRCTPushNotification.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libRCTPushNotification.a;
|
||||
remoteRef = 7F63D2801E6C957C001FAE12 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
7F63D2831E6C957C001FAE12 /* libRCTPushNotification-tvOS.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = "libRCTPushNotification-tvOS.a";
|
||||
remoteRef = 7F63D2821E6C957C001FAE12 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
7FDF28E11E1F4B1F00DBBE56 /* libRNSVG.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
|
|
@ -988,7 +1040,6 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
|
|
@ -1011,7 +1062,6 @@
|
|||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
INFOPLIST_FILE = MattermostTests/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
|
|
@ -1029,10 +1079,11 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
HEADER_SEARCH_PATHS = "$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS/**";
|
||||
INFOPLIST_FILE = Mattermost/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
OTHER_LDFLAGS = (
|
||||
|
|
@ -1052,10 +1103,11 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
HEADER_SEARCH_PATHS = "$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS/**";
|
||||
INFOPLIST_FILE = Mattermost/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
OTHER_LDFLAGS = (
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import "RCTPushNotificationManager.h"
|
||||
#import <React/RCTBundleURLProvider.h>
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
|
|
@ -34,4 +34,31 @@
|
|||
return YES;
|
||||
}
|
||||
|
||||
// Required to register for notifications
|
||||
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
|
||||
{
|
||||
[RCTPushNotificationManager didRegisterUserNotificationSettings:notificationSettings];
|
||||
}
|
||||
// Required for the register event.
|
||||
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
|
||||
{
|
||||
[RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
|
||||
}
|
||||
// Required for the notification event. You must call the completion handler after handling the remote notification.
|
||||
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
|
||||
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
|
||||
{
|
||||
[RCTPushNotificationManager didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
|
||||
}
|
||||
// Required for the registrationError event.
|
||||
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
|
||||
{
|
||||
[RCTPushNotificationManager didFailToRegisterForRemoteNotificationsWithError:error];
|
||||
}
|
||||
// Required for the localNotification event.
|
||||
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
|
||||
{
|
||||
[RCTPushNotificationManager didReceiveLocalNotification:notification];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -20,10 +20,14 @@
|
|||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>12</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
|
|
@ -72,7 +76,5 @@
|
|||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
8
ios/Mattermost/Mattermost.entitlements
Normal file
8
ios/Mattermost/Mattermost.entitlements
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -16,6 +16,8 @@
|
|||
"react-native-drawer": "2.3.0",
|
||||
"react-native-keyboard-aware-scroll-view": "0.2.7",
|
||||
"react-native-keyboard-spacer": "0.3.1",
|
||||
"react-native-message-bar": "1.6.0",
|
||||
"react-native-push-notification": "2.2.1",
|
||||
"react-native-search-bar": "enahum/react-native-search-bar.git",
|
||||
"react-native-svg": "4.5.0",
|
||||
"react-native-vector-icons": "4.0.0",
|
||||
|
|
|
|||
|
|
@ -94,11 +94,18 @@ export function setServerVersion(serverVersion) {
|
|||
};
|
||||
}
|
||||
|
||||
export function setDeviceToken(token) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({type: GeneralTypes.RECEIVED_APP_DEVICE_TOKEN, data: token}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
getPing,
|
||||
getClientConfig,
|
||||
getLicenseConfig,
|
||||
logClientError,
|
||||
setAppState,
|
||||
setServerVersion
|
||||
setServerVersion,
|
||||
setDeviceToken
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ export function login(loginId, password, mfaToken = '') {
|
|||
return async (dispatch, getState) => {
|
||||
dispatch({type: UsersTypes.LOGIN_REQUEST}, getState);
|
||||
|
||||
return Client.login(loginId, password, mfaToken).
|
||||
const deviceId = getState().entities.general.deviceToken;
|
||||
|
||||
return Client.login(loginId, password, mfaToken, deviceId).
|
||||
then(async (data) => {
|
||||
let teamMembers;
|
||||
let preferences;
|
||||
|
|
@ -101,6 +103,11 @@ export function loadMe() {
|
|||
return;
|
||||
}
|
||||
|
||||
const deviceId = getState().entities.general.deviceToken;
|
||||
if (deviceId) {
|
||||
Client.attachDevice(deviceId);
|
||||
}
|
||||
|
||||
let preferences;
|
||||
dispatch({type: PreferencesTypes.MY_PREFERENCES_REQUEST}, getState);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -213,11 +213,12 @@ export default class Client {
|
|||
);
|
||||
};
|
||||
|
||||
login = async (loginId, password, token = '') => {
|
||||
login = async (loginId, password, token = '', deviceId = '') => {
|
||||
const body = {
|
||||
login_id: loginId,
|
||||
password,
|
||||
token
|
||||
token,
|
||||
device_id: deviceId
|
||||
};
|
||||
|
||||
const {headers, data} = await this.doFetchWithResponse(
|
||||
|
|
@ -244,6 +245,13 @@ export default class Client {
|
|||
return response;
|
||||
};
|
||||
|
||||
attachDevice = async (deviceId) => {
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}/attach_device`,
|
||||
{method: 'post', body: JSON.stringify({device_id: deviceId})}
|
||||
);
|
||||
};
|
||||
|
||||
updateUser = async (user) => {
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}/update`,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,10 @@ const Constants = {
|
|||
START_OF_NEW_MESSAGES: 'start-of-new-messages',
|
||||
|
||||
POST_HEADER_CHANGE: 'system_header_change',
|
||||
POST_PURPOSE_CHANGE: 'system_purpose_change'
|
||||
POST_PURPOSE_CHANGE: 'system_purpose_change',
|
||||
|
||||
PUSH_NOTIFY_APPLE_REACT_NATIVE: 'apple_rn',
|
||||
PUSH_NOTIFY_ANDROID_REACT_NATIVE: 'android_rn'
|
||||
};
|
||||
|
||||
const FileConstants = {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const GeneralTypes = keyMirror({
|
|||
RECEIVED_APP_STATE: null,
|
||||
RECEIVED_APP_CREDENTIALS: null,
|
||||
REMOVED_APP_CREDENTIALS: null,
|
||||
RECEIVED_APP_DEVICE_TOKEN: null,
|
||||
|
||||
PING_REQUEST: null,
|
||||
PING_SUCCESS: null,
|
||||
|
|
|
|||
|
|
@ -59,10 +59,23 @@ function serverVersion(state = '', action) {
|
|||
}
|
||||
}
|
||||
|
||||
function deviceToken(state = '', action) {
|
||||
switch (action.type) {
|
||||
case GeneralTypes.RECEIVED_APP_DEVICE_TOKEN:
|
||||
return action.data;
|
||||
|
||||
case UsersTypes.LOGOUT_SUCCESS:
|
||||
return '';
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
appState,
|
||||
credentials,
|
||||
config,
|
||||
license,
|
||||
serverVersion
|
||||
serverVersion,
|
||||
deviceToken
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue