Merge branch 'master' into mark-as-unread
This commit is contained in:
commit
c27e96579f
36 changed files with 1826 additions and 739 deletions
|
|
@ -1,5 +1,206 @@
|
|||
version: 2.1
|
||||
|
||||
executors:
|
||||
android:
|
||||
parameters:
|
||||
resource_class:
|
||||
default: large
|
||||
type: string
|
||||
environment:
|
||||
NODE_OPTIONS: --max_old_space_size=12000
|
||||
NODE_ENV: production
|
||||
BABEL_ENV: production
|
||||
docker:
|
||||
- image: circleci/android:api-27-node
|
||||
working_directory: ~/mattermost-mobile
|
||||
resource_class: <<parameters.resource_class>>
|
||||
|
||||
ios:
|
||||
environment:
|
||||
NODE_OPTIONS: --max_old_space_size=12000
|
||||
NODE_ENV: production
|
||||
BABEL_ENV: production
|
||||
macos:
|
||||
xcode: "11.0.0"
|
||||
working_directory: ~/mattermost-mobile
|
||||
shell: /bin/bash --login -o pipefail
|
||||
|
||||
commands:
|
||||
checkout-private:
|
||||
description: "Checkout the private repo with build env vars"
|
||||
steps:
|
||||
- add_ssh_keys:
|
||||
fingerprints:
|
||||
- "59:4d:99:5e:1c:6d:30:36:6d:60:76:88:ff:a7:ab:63"
|
||||
- run:
|
||||
name: Clone the mobile private repo
|
||||
command: git clone git@github.com:mattermost/mattermost-mobile-private.git ~/mattermost-mobile-private
|
||||
|
||||
fastlane-dependencies:
|
||||
description: "Get Fastlane dependencies"
|
||||
parameters:
|
||||
for:
|
||||
type: string
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore Fastlane cache
|
||||
key: v1-gems-<< parameters.for >>-{{ checksum "fastlane/Gemfile.lock" }}-{{ arch }}
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Download Fastlane dependencies
|
||||
command: bundle install --path vendor/bundle
|
||||
- save_cache:
|
||||
name: Save Fastlane cache
|
||||
key: v1-gems-<< parameters.for >>-{{ checksum "fastlane/Gemfile.lock" }}-{{ arch }}
|
||||
paths:
|
||||
- fastlane/vendor/bundle
|
||||
|
||||
gradle-dependencies:
|
||||
description: "Get Gradle dependencies"
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore Gradle cache
|
||||
key: v1-gradle-{{ checksum "android/build.gradle" }}-{{ checksum "android/app/build.gradle" }}
|
||||
- run:
|
||||
working_directory: android
|
||||
name: Download Gradle dependencies
|
||||
command: ./gradlew dependencies
|
||||
- save_cache:
|
||||
name: Save Gradle cache
|
||||
paths:
|
||||
- ~/.gradle
|
||||
key: v1-gradle-{{ checksum "android/build.gradle" }}-{{ checksum "android/app/build.gradle" }}
|
||||
|
||||
assets:
|
||||
description: "Generate app assets"
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore assets cache
|
||||
key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }}
|
||||
- run:
|
||||
name: Generate assets
|
||||
command: make dist/assets
|
||||
- save_cache:
|
||||
name: Save assets cache
|
||||
key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }}
|
||||
paths:
|
||||
- dist
|
||||
|
||||
npm-dependencies:
|
||||
description: "Get JavaScript dependencies"
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore npm cache
|
||||
key: v1-npm-{{ checksum "package.json" }}-{{ arch }}
|
||||
- run:
|
||||
name: Getting JavaScript dependencies
|
||||
command: NODE_ENV=development npm install
|
||||
- save_cache:
|
||||
name: Save npm cache
|
||||
key: v1-npm-{{ checksum "package.json" }}-{{ arch }}
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
pods-dependencies:
|
||||
description: "Get cocoapods dependencies"
|
||||
steps:
|
||||
- restore_cache:
|
||||
name: Restore cocoapods specs and pods
|
||||
key: v1-cocoapods-{{ checksum "ios/Podfile.lock" }}-{{ arch }}
|
||||
- run:
|
||||
name: Getting cocoapods dependencies
|
||||
working_directory: ios
|
||||
command: pod install
|
||||
- save_cache:
|
||||
name: Save cocoapods specs and pods cache
|
||||
key: v1-cocoapods-{{ checksum "ios/Podfile.lock" }}-{{ arch }}
|
||||
paths:
|
||||
- ios/Pods
|
||||
- ~/.cocoapods
|
||||
|
||||
build-android:
|
||||
description: "Build the android app"
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- checkout-private
|
||||
- npm-dependencies
|
||||
- assets
|
||||
- fastlane-dependencies:
|
||||
for: android
|
||||
- gradle-dependencies
|
||||
- run:
|
||||
name: Append Keystore to build Android
|
||||
command: |
|
||||
cp ~/mattermost-mobile-private/android/${STORE_FILE} android/app/${STORE_FILE}
|
||||
echo "" | tee -a android/gradle.properties > /dev/null
|
||||
echo MATTERMOST_RELEASE_STORE_FILE=${STORE_FILE} | tee -a android/gradle.properties > /dev/null
|
||||
echo ${STORE_ALIAS} | tee -a android/gradle.properties > /dev/null
|
||||
echo ${STORE_PASSWORD} | tee -a android/gradle.properties > /dev/null
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build android
|
||||
no_output_timeout: 30m
|
||||
command: bundle exec fastlane android build || exit 1
|
||||
|
||||
build-ios:
|
||||
description: "Build the iOS app"
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- npm-dependencies
|
||||
- pods-dependencies
|
||||
- assets
|
||||
- fastlane-dependencies:
|
||||
for: ios
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build iOS
|
||||
no_output_timeout: 30m
|
||||
command: bundle exec fastlane ios build || exit 1
|
||||
|
||||
deploy-android:
|
||||
description: "Deploy apk to Google Play"
|
||||
parameters:
|
||||
apk_path:
|
||||
type: string
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/mattermost-mobile
|
||||
- deploy:
|
||||
name: "Deploy apk to Google Play"
|
||||
working_directory: fastlane
|
||||
command: bundle exec fastlane android deploy apk:../<<parameters.apk_path>>
|
||||
|
||||
deploy-ios:
|
||||
description: "Deploy ipa to TestFlight"
|
||||
parameters:
|
||||
ipa_path:
|
||||
type: string
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/mattermost-mobile
|
||||
- deploy:
|
||||
name: "Deploy ipa to TestFlight"
|
||||
working_directory: fastlane
|
||||
command: bundle exec fastlane ios deploy ipa:../<<parameters.ipa_path>>
|
||||
|
||||
persist:
|
||||
description: "Persist mattermost-mobile directory"
|
||||
steps:
|
||||
- persist_to_workspace:
|
||||
root: ./
|
||||
paths:
|
||||
- ./
|
||||
|
||||
save:
|
||||
description: "Save binaries artifacts"
|
||||
parameters:
|
||||
filename:
|
||||
type: string
|
||||
steps:
|
||||
- store_artifacts:
|
||||
path: ~/mattermost-mobile/<<parameters.filename>>
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
|
@ -7,17 +208,201 @@ jobs:
|
|||
docker:
|
||||
- image: circleci/node:10
|
||||
steps:
|
||||
- checkout
|
||||
- run: |
|
||||
echo assets/base/config.json
|
||||
cat assets/base/config.json
|
||||
# Avoid installing pods
|
||||
touch .podinstall
|
||||
# Run tests
|
||||
make test || exit 1
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- npm-dependencies
|
||||
- assets
|
||||
- run:
|
||||
name: Check styles
|
||||
command: npm run check
|
||||
- run:
|
||||
name: Running Tests
|
||||
command: npm test
|
||||
- run:
|
||||
name: Check i18n
|
||||
command: make i18n-extract-ci
|
||||
|
||||
build-android-beta:
|
||||
executor: android
|
||||
steps:
|
||||
- build-android
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost_Beta.apk"
|
||||
|
||||
build-android-release:
|
||||
executor: android
|
||||
steps:
|
||||
- build-android
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost.apk"
|
||||
|
||||
build-android-pr:
|
||||
executor: android
|
||||
environment:
|
||||
BRANCH_TO_BUILD: ${CIRCLE_BRANCH}
|
||||
steps:
|
||||
- build-android
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost_Beta.apk"
|
||||
|
||||
build-ios-beta:
|
||||
executor: ios
|
||||
steps:
|
||||
- build-ios
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost_Beta.ipa"
|
||||
|
||||
build-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
- build-ios
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost.ipa"
|
||||
|
||||
build-ios-pr:
|
||||
executor: ios
|
||||
environment:
|
||||
BRANCH_TO_BUILD: ${CIRCLE_BRANCH}
|
||||
steps:
|
||||
- build-ios
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost_Beta.ipa"
|
||||
|
||||
deploy-android-release:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- deploy-android:
|
||||
apk_path: Mattermost.apk
|
||||
|
||||
deploy-android-beta:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- deploy-android:
|
||||
apk_path: Mattermost_Beta.apk
|
||||
|
||||
deploy-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
- deploy-ios:
|
||||
ipa_path: Mattermost.ipa
|
||||
|
||||
deploy-ios-beta:
|
||||
executor: ios
|
||||
steps:
|
||||
- deploy-ios:
|
||||
ipa_path: Mattermost_Beta.ipa
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
pr-test:
|
||||
build:
|
||||
jobs:
|
||||
- test
|
||||
|
||||
- build-android-release:
|
||||
context: mattermost-mobile-android-release
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-release-\d+$/
|
||||
- deploy-android-release:
|
||||
context: mattermost-mobile-android-release
|
||||
requires:
|
||||
- build-android-release
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-release-\d+$/
|
||||
|
||||
- build-android-beta:
|
||||
context: mattermost-mobile-android-beta
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-beta-\d+$/
|
||||
- deploy-android-beta:
|
||||
context: mattermost-mobile-android-beta
|
||||
requires:
|
||||
- build-android-beta
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-android-\d+$/
|
||||
- /^build-android-beta-\d+$/
|
||||
|
||||
- build-ios-release:
|
||||
context: mattermost-mobile-ios-release
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-release-\d+$/
|
||||
- deploy-ios-release:
|
||||
context: mattermost-mobile-ios-release
|
||||
requires:
|
||||
- build-ios-release
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-release-\d+$/
|
||||
|
||||
- build-ios-beta:
|
||||
context: mattermost-mobile-ios-beta
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-beta-\d+$/
|
||||
- deploy-ios-beta:
|
||||
context: mattermost-mobile-ios-beta
|
||||
requires:
|
||||
- build-ios-beta
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /^build-\d+$/
|
||||
- /^build-ios-\d+$/
|
||||
- /^build-ios-beta-\d+$/
|
||||
|
||||
- build-android-pr:
|
||||
context: mattermost-mobile-android-pr
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only: /^build-pr-.*/
|
||||
- build-ios-pr:
|
||||
context: mattermost-mobile-ios-pr
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
branches:
|
||||
only: /^build-pr-.*/
|
||||
|
|
|
|||
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -22,7 +22,6 @@ build/
|
|||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
xcshareddata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
|
|
@ -31,7 +30,8 @@ DerivedData
|
|||
*.apk
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
xcshareddata/
|
||||
ios/Pods
|
||||
.podinstall
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
|
|
@ -85,10 +85,6 @@ ios/sentry.properties
|
|||
.nyc_output
|
||||
coverage
|
||||
|
||||
# Pods
|
||||
.podinstall
|
||||
ios/Pods/
|
||||
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
|
||||
|
|
|
|||
17
Makefile
17
Makefile
|
|
@ -64,25 +64,16 @@ check-style: node_modules ## Runs eslint
|
|||
clean: ## Cleans dependencies, previous builds and temp files
|
||||
@echo Cleaning started
|
||||
|
||||
@rm -rf node_modules
|
||||
@rm -f .podinstall
|
||||
@rm -rf ios/Pods
|
||||
@rm -rf node_modules
|
||||
@rm -rf dist
|
||||
@rm -rf ios/build
|
||||
@rm -rf ios/Pods
|
||||
@rm -rf android/app/build
|
||||
|
||||
@echo Cleanup finished
|
||||
|
||||
post-install:
|
||||
@# Need to copy custom RNDocumentPicker.m that implements direct access to the document picker in iOS
|
||||
@cp ./native_modules/RNDocumentPicker.m node_modules/react-native-document-picker/ios/RNDocumentPicker/RNDocumentPicker.m
|
||||
|
||||
@# Need to copy custom RNCookieManagerIOS.m that fixes a crash when cookies does not have expiration date set
|
||||
@cp ./native_modules/RNCookieManagerIOS.m node_modules/react-native-cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m
|
||||
|
||||
@# Need to copy custom RNCNetInfo.m that checks for internet connectivity instead of reaching a host by default
|
||||
@cp ./native_modules/RNCNetInfo.m node_modules/@react-native-community/netinfo/ios/RNCNetInfo.m
|
||||
|
||||
@rm -f node_modules/intl/.babelrc
|
||||
@# Hack to get react-intl and its dependencies to work with react-native
|
||||
@# Based off of https://github.com/este/este/blob/master/gulp/native-fix.js
|
||||
|
|
@ -90,10 +81,6 @@ post-install:
|
|||
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-messageformat/package.json
|
||||
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-relativeformat/package.json
|
||||
@sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json
|
||||
@if [ $(shell grep "const Platform" node_modules/react-native/Libraries/Lists/VirtualizedList.js | grep -civ grep) -eq 0 ]; then \
|
||||
sed $ -i'' -e "s|const ReactNative = require('ReactNative');|const ReactNative = require('ReactNative');`echo $\\\\\\r;`const Platform = require('Platform');|g" node_modules/react-native/Libraries/Lists/VirtualizedList.js; \
|
||||
fi
|
||||
@sed -i'' -e 's|transform: \[{scaleY: -1}\],|...Platform.select({android: {transform: \[{perspective: 1}, {scaleY: -1}\]}, ios: {transform: \[{scaleY: -1}\]}}),|g' node_modules/react-native/Libraries/Lists/VirtualizedList.js
|
||||
|
||||
@./node_modules/.bin/patch-package
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import android.net.Uri;
|
|||
import android.os.Bundle;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings.System;
|
||||
import android.util.Log;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
|
@ -34,7 +33,6 @@ import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
|||
import com.wix.reactnativenotifications.core.AppLaunchHelper;
|
||||
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
|
||||
import com.wix.reactnativenotifications.core.JsIOHelper;
|
||||
import com.wix.reactnativenotifications.helpers.ApplicationBadgeHelper;
|
||||
|
||||
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
|
||||
|
||||
|
|
@ -49,6 +47,9 @@ public class CustomPushNotification extends PushNotification {
|
|||
private static final String PUSH_TYPE_CLEAR = "clear";
|
||||
private static final String PUSH_TYPE_UPDATE_BADGE = "update_badge";
|
||||
|
||||
private NotificationChannel mHighImportanceChannel;
|
||||
private NotificationChannel mMinImportanceChannel;
|
||||
|
||||
private static Map<String, Integer> channelIdToNotificationCount = new HashMap<String, Integer>();
|
||||
private static Map<String, List<Bundle>> channelIdToNotification = new HashMap<String, List<Bundle>>();
|
||||
private static AppLifecycleFacade lifecycleFacade;
|
||||
|
|
@ -58,6 +59,7 @@ public class CustomPushNotification extends PushNotification {
|
|||
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
|
||||
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
|
||||
this.context = context;
|
||||
createNotificationChannels();
|
||||
}
|
||||
|
||||
public static void clearNotification(Context mContext, int notificationId, String channelId) {
|
||||
|
|
@ -77,7 +79,6 @@ public class CustomPushNotification extends PushNotification {
|
|||
if (count != -1) {
|
||||
int total = CustomPushNotification.badgeCount - count;
|
||||
int badgeCount = total < 0 ? 0 : total;
|
||||
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), badgeCount);
|
||||
CustomPushNotification.badgeCount = badgeCount;
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +91,6 @@ public class CustomPushNotification extends PushNotification {
|
|||
if (mContext != null) {
|
||||
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancelAll();
|
||||
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,59 +158,141 @@ public class CustomPushNotification extends PushNotification {
|
|||
digestNotification();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postNotification(int id, Notification notification) {
|
||||
boolean force = false;
|
||||
Bundle bundle = notification.extras;
|
||||
if (bundle != null) {
|
||||
force = bundle.getBoolean("localTest");
|
||||
}
|
||||
|
||||
if (!mAppLifecycleFacade.isAppVisible() || force) {
|
||||
super.postNotification(id, notification);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
|
||||
final Resources res = mContext.getResources();
|
||||
String packageName = mContext.getPackageName();
|
||||
NotificationPreferences notificationPreferences = NotificationPreferences.getInstance(mContext);
|
||||
|
||||
// First, get a builder initialized with defaults from the core class.
|
||||
final Notification.Builder notification = new Notification.Builder(mContext);
|
||||
|
||||
// If Android Oreo or above we need to register a channel
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
String CHANNEL_ID = "channel_01";
|
||||
String CHANNEL_NAME = "Mattermost notifications";
|
||||
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
|
||||
CHANNEL_NAME,
|
||||
NotificationManager.IMPORTANCE_HIGH);
|
||||
channel.setShowBadge(true);
|
||||
|
||||
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
notification.setChannelId(CHANNEL_ID);
|
||||
}
|
||||
|
||||
Bundle bundle = mNotificationProps.asBundle();
|
||||
|
||||
String version = bundle.getString("version");
|
||||
addNotificationExtras(notification, bundle);
|
||||
setNotificationIcons(notification, bundle);
|
||||
setNotificationMessagingStyle(notification, bundle);
|
||||
setNotificationChannel(notification, bundle);
|
||||
setNotificationBadgeIconType(notification);
|
||||
|
||||
NotificationPreferences notificationPreferences = NotificationPreferences.getInstance(mContext);
|
||||
setNotificationSound(notification, notificationPreferences);
|
||||
setNotificationVibrate(notification, notificationPreferences);
|
||||
setNotificationBlink(notification, notificationPreferences);
|
||||
|
||||
String channelId = bundle.getString("channel_id");
|
||||
String channelName = bundle.getString("channel_name");
|
||||
String senderName = bundle.getString("sender_name");
|
||||
String senderId = bundle.getString("sender_id");
|
||||
String postId = bundle.getString("post_id");
|
||||
String badge = bundle.getString("badge");
|
||||
int notificationId = channelId != null ? channelId.hashCode() : MESSAGE_NOTIFICATION_ID;
|
||||
setNotificationNumber(notification, channelId);
|
||||
setNotificationDeleteIntent(notification, notificationId);
|
||||
addNotificationReplyAction(notification, notificationId, bundle);
|
||||
|
||||
notification
|
||||
.setContentIntent(intent)
|
||||
.setVisibility(Notification.VISIBILITY_PRIVATE)
|
||||
.setPriority(Notification.PRIORITY_HIGH)
|
||||
.setAutoCancel(true);
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
private void addNotificationExtras(Notification.Builder notification, Bundle bundle) {
|
||||
Bundle userInfoBundle = bundle.getBundle("userInfo");
|
||||
if (userInfoBundle == null) {
|
||||
userInfoBundle = new Bundle();
|
||||
}
|
||||
|
||||
String channelId = bundle.getString("channel_id");
|
||||
userInfoBundle.putString("channel_id", channelId);
|
||||
|
||||
notification.addExtras(userInfoBundle);
|
||||
}
|
||||
|
||||
private void setNotificationIcons(Notification.Builder notification, Bundle bundle) {
|
||||
String smallIcon = bundle.getString("smallIcon");
|
||||
String largeIcon = bundle.getString("largeIcon");
|
||||
int notificationId = channelId != null ? channelId.hashCode() : MESSAGE_NOTIFICATION_ID;
|
||||
|
||||
int smallIconResId = getSmallIconResourceId(smallIcon);
|
||||
notification.setSmallIcon(smallIconResId);
|
||||
|
||||
int largeIconResId = getLargeIconResourceId(largeIcon);
|
||||
final Resources res = mContext.getResources();
|
||||
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
|
||||
if (largeIconResId != 0 && (largeIconBitmap != null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
|
||||
notification.setLargeIcon(largeIconBitmap);
|
||||
}
|
||||
}
|
||||
|
||||
private int getSmallIconResourceId(String iconName) {
|
||||
if (iconName == null) {
|
||||
iconName = "ic_notification";
|
||||
}
|
||||
|
||||
int resourceId = getIconResourceId(iconName);
|
||||
|
||||
if (resourceId == 0) {
|
||||
iconName = "ic_launcher";
|
||||
resourceId = getIconResourceId(iconName);
|
||||
|
||||
if (resourceId == 0) {
|
||||
resourceId = android.R.drawable.ic_dialog_info;
|
||||
}
|
||||
}
|
||||
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
private int getLargeIconResourceId(String iconName) {
|
||||
if (iconName == null) {
|
||||
iconName = "ic_launcher";
|
||||
}
|
||||
|
||||
return getIconResourceId(iconName);
|
||||
}
|
||||
|
||||
private int getIconResourceId(String iconName) {
|
||||
final Resources res = mContext.getResources();
|
||||
String packageName = mContext.getPackageName();
|
||||
String defType = "mipmap";
|
||||
|
||||
return res.getIdentifier(iconName, defType, packageName);
|
||||
}
|
||||
|
||||
private void setNotificationNumber(Notification.Builder notification, String channelId) {
|
||||
Integer number = channelIdToNotificationCount.get(channelId);
|
||||
if (number != null) {
|
||||
number = 0;
|
||||
}
|
||||
notification.setNumber(number);
|
||||
}
|
||||
|
||||
private void setNotificationMessagingStyle(Notification.Builder notification, Bundle bundle) {
|
||||
Notification.MessagingStyle messagingStyle = getMessagingStyle(bundle);
|
||||
notification.setStyle(messagingStyle);
|
||||
}
|
||||
|
||||
private Notification.MessagingStyle getMessagingStyle(Bundle bundle) {
|
||||
Notification.MessagingStyle messagingStyle;
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
messagingStyle = new Notification.MessagingStyle("");
|
||||
} else {
|
||||
String senderId = bundle.getString("sender_id");
|
||||
Person sender = new Person.Builder()
|
||||
.setKey(senderId)
|
||||
.setName("")
|
||||
.build();
|
||||
messagingStyle = new Notification.MessagingStyle(sender);
|
||||
}
|
||||
|
||||
String conversationTitle = getConversationTitle(bundle);
|
||||
setMessagingStyleConversationTitle(messagingStyle, conversationTitle, bundle);
|
||||
addMessagingStyleMessages(messagingStyle, conversationTitle, bundle);
|
||||
|
||||
return messagingStyle;
|
||||
}
|
||||
|
||||
private String getConversationTitle(Bundle bundle) {
|
||||
String title = null;
|
||||
|
||||
String version = bundle.getString("version");
|
||||
if (version != null && version.equals("v2")) {
|
||||
title = channelName;
|
||||
title = bundle.getString("channel_name");
|
||||
} else {
|
||||
title = bundle.getString("title");
|
||||
}
|
||||
|
|
@ -220,149 +302,100 @@ public class CustomPushNotification extends PushNotification {
|
|||
title = mContext.getPackageManager().getApplicationLabel(appInfo).toString();
|
||||
}
|
||||
|
||||
Bundle b = bundle.getBundle("userInfo");
|
||||
if (b == null) {
|
||||
b = new Bundle();
|
||||
}
|
||||
b.putString("channel_id", channelId);
|
||||
notification.addExtras(b);
|
||||
|
||||
int smallIconResId;
|
||||
int largeIconResId;
|
||||
|
||||
if (smallIcon != null) {
|
||||
smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName);
|
||||
} else {
|
||||
smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
|
||||
}
|
||||
|
||||
if (smallIconResId == 0) {
|
||||
smallIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
|
||||
|
||||
if (smallIconResId == 0) {
|
||||
smallIconResId = android.R.drawable.ic_dialog_info;
|
||||
}
|
||||
}
|
||||
|
||||
if (largeIcon != null) {
|
||||
largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName);
|
||||
} else {
|
||||
largeIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
|
||||
}
|
||||
|
||||
if (badge != null) {
|
||||
int badgeCount = Integer.parseInt(badge);
|
||||
CustomPushNotification.badgeCount = badgeCount;
|
||||
notification.setNumber(badgeCount);
|
||||
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), CustomPushNotification.badgeCount);
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
private void setMessagingStyleConversationTitle(Notification.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
|
||||
String channelName = bundle.getString("channel_name");
|
||||
String senderName = bundle.getString("sender_name");
|
||||
if (android.text.TextUtils.isEmpty(senderName)) {
|
||||
senderName = getSenderName(senderName, channelName, bundle.getString("message"));
|
||||
senderName = getSenderName(bundle);
|
||||
}
|
||||
|
||||
String personId = senderId;
|
||||
if (!android.text.TextUtils.isEmpty(channelName)) {
|
||||
personId = channelId;
|
||||
if (conversationTitle != null && (!conversationTitle.startsWith("@") || channelName != senderName)) {
|
||||
messagingStyle.setConversationTitle(conversationTitle);
|
||||
}
|
||||
}
|
||||
|
||||
Notification.MessagingStyle messagingStyle;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
messagingStyle = new Notification.MessagingStyle("");
|
||||
} else {
|
||||
Person sender = new Person.Builder()
|
||||
.setKey(senderId)
|
||||
.setName("")
|
||||
.build();
|
||||
messagingStyle = new Notification.MessagingStyle(sender);
|
||||
}
|
||||
|
||||
if (title != null && (!title.startsWith("@") || channelName != senderName)) {
|
||||
messagingStyle
|
||||
.setConversationTitle(title);
|
||||
}
|
||||
private void addMessagingStyleMessages(Notification.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
|
||||
List<Bundle> bundleList;
|
||||
|
||||
String channelId = bundle.getString("channel_id");
|
||||
List<Bundle> bundleArray = channelIdToNotification.get(channelId);
|
||||
List<Bundle> list;
|
||||
if (bundleArray != null) {
|
||||
list = new ArrayList<Bundle>(bundleArray);
|
||||
bundleList = new ArrayList<Bundle>(bundleArray);
|
||||
} else {
|
||||
list = new ArrayList<Bundle>();
|
||||
list.add(bundle);
|
||||
bundleList = new ArrayList<Bundle>();
|
||||
bundleList.add(bundle);
|
||||
}
|
||||
|
||||
int listCount = list.size() - 1;
|
||||
for (int i = listCount; i >= 0; i--) {
|
||||
Bundle data = list.get(i);
|
||||
int bundleCount = bundleList.size() - 1;
|
||||
for (int i = bundleCount; i >= 0; i--) {
|
||||
Bundle data = bundleList.get(i);
|
||||
String message = data.getString("message");
|
||||
String previousPersonName = getSenderName(data.getString("sender_name"), channelName, message);
|
||||
String previousPersonId = data.getString("sender_id");
|
||||
|
||||
if (title == null || !android.text.TextUtils.isEmpty(previousPersonName)) {
|
||||
message = removeSenderFromMessage(previousPersonName, channelName, message);
|
||||
String senderId = data.getString("sender_id");
|
||||
Bundle userInfoBundle = data.getBundle("userInfo");
|
||||
String senderName = getSenderName(data);
|
||||
if (userInfoBundle != null) {
|
||||
boolean localPushNotificationTest = userInfoBundle.getBoolean("localTest");
|
||||
if (localPushNotificationTest) {
|
||||
senderName = "Test";
|
||||
}
|
||||
}
|
||||
|
||||
if (conversationTitle == null || !android.text.TextUtils.isEmpty(senderName.trim())) {
|
||||
message = removeSenderNameFromMessage(message, senderName);
|
||||
}
|
||||
|
||||
long timestamp = data.getLong("time");
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
messagingStyle.addMessage(message, data.getLong("time"), previousPersonName);
|
||||
messagingStyle.addMessage(message, timestamp, senderName);
|
||||
} else {
|
||||
Person sender = new Person.Builder()
|
||||
.setKey(previousPersonId)
|
||||
.setName(previousPersonName)
|
||||
.build();
|
||||
messagingStyle.addMessage(message, data.getLong("time"), sender);
|
||||
.setKey(senderId)
|
||||
.setName(senderName)
|
||||
.build();
|
||||
messagingStyle.addMessage(message, timestamp, sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notification
|
||||
.setContentIntent(intent)
|
||||
.setGroupSummary(true)
|
||||
.setStyle(messagingStyle)
|
||||
.setSmallIcon(smallIconResId)
|
||||
.setVisibility(Notification.VISIBILITY_PRIVATE)
|
||||
.setPriority(Notification.PRIORITY_HIGH)
|
||||
.setAutoCancel(true);
|
||||
private void setNotificationChannel(Notification.Builder notification, Bundle bundle) {
|
||||
// If Android Oreo or above we need to register a channel
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationChannel notificationChannel = mHighImportanceChannel;
|
||||
|
||||
boolean localPushNotificationTest = false;
|
||||
Bundle userInfoBundle = bundle.getBundle("userInfo");
|
||||
if (userInfoBundle != null) {
|
||||
localPushNotificationTest = userInfoBundle.getBoolean("localTest");
|
||||
}
|
||||
|
||||
if (mAppLifecycleFacade.isAppVisible() && !localPushNotificationTest) {
|
||||
notificationChannel = mMinImportanceChannel;
|
||||
}
|
||||
|
||||
notification.setChannelId(notificationChannel.getId());
|
||||
}
|
||||
|
||||
private void setNotificationBadgeIconType(Notification.Builder notification) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
notification.setBadgeIconType(Notification.BADGE_ICON_SMALL);
|
||||
}
|
||||
}
|
||||
|
||||
// Let's add a delete intent when the notification is dismissed
|
||||
Intent delIntent = new Intent(mContext, NotificationDismissService.class);
|
||||
delIntent.putExtra(NOTIFICATION_ID, notificationId);
|
||||
PendingIntent deleteIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, delIntent, mNotificationProps);
|
||||
notification.setDeleteIntent(deleteIntent);
|
||||
|
||||
private void setNotificationGroup(Notification.Builder notification) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
notification.setGroup(GROUP_KEY_MESSAGES);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && postId != null) {
|
||||
Intent replyIntent = new Intent(mContext, NotificationReplyBroadcastReceiver.class);
|
||||
replyIntent.setAction(KEY_TEXT_REPLY);
|
||||
replyIntent.putExtra(NOTIFICATION_ID, notificationId);
|
||||
replyIntent.putExtra("pushNotification", bundle);
|
||||
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
|
||||
.setLabel("Reply")
|
||||
.build();
|
||||
|
||||
Notification.Action replyAction = new Notification.Action.Builder(
|
||||
R.drawable.ic_notif_action_reply, "Reply", replyPendingIntent)
|
||||
.addRemoteInput(remoteInput)
|
||||
.setAllowGeneratedReplies(true)
|
||||
.build();
|
||||
|
||||
notification
|
||||
.setShowWhen(true)
|
||||
.addAction(replyAction);
|
||||
}
|
||||
|
||||
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
|
||||
if (largeIconResId != 0 && (largeIcon != null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
|
||||
notification.setLargeIcon(largeIconBitmap);
|
||||
.setGroup(GROUP_KEY_MESSAGES)
|
||||
.setGroupSummary(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void setNotificationSound(Notification.Builder notification, NotificationPreferences notificationPreferences) {
|
||||
String soundUri = notificationPreferences.getNotificationSound();
|
||||
if (soundUri != null) {
|
||||
if (soundUri != "none") {
|
||||
|
|
@ -372,19 +405,62 @@ public class CustomPushNotification extends PushNotification {
|
|||
Uri defaultUri = System.DEFAULT_NOTIFICATION_URI;
|
||||
notification.setSound(defaultUri, AudioManager.STREAM_NOTIFICATION);
|
||||
}
|
||||
}
|
||||
|
||||
private void setNotificationVibrate(Notification.Builder notification, NotificationPreferences notificationPreferences) {
|
||||
boolean vibrate = notificationPreferences.getShouldVibrate();
|
||||
if (vibrate) {
|
||||
// use the system default for vibration
|
||||
// Use the system default for vibration
|
||||
notification.setDefaults(Notification.DEFAULT_VIBRATE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setNotificationBlink(Notification.Builder notification, NotificationPreferences notificationPreferences) {
|
||||
boolean blink = notificationPreferences.getShouldBlink();
|
||||
if (blink) {
|
||||
notification.setLights(Color.CYAN, 500, 500);
|
||||
}
|
||||
}
|
||||
|
||||
return notification;
|
||||
private void setNotificationDeleteIntent(Notification.Builder notification, int notificationId) {
|
||||
// Let's add a delete intent when the notification is dismissed
|
||||
Intent delIntent = new Intent(mContext, NotificationDismissService.class);
|
||||
delIntent.putExtra(NOTIFICATION_ID, notificationId);
|
||||
PendingIntent deleteIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, delIntent, mNotificationProps);
|
||||
notification.setDeleteIntent(deleteIntent);
|
||||
}
|
||||
|
||||
private void addNotificationReplyAction(Notification.Builder notification, int notificationId, Bundle bundle) {
|
||||
String postId = bundle.getString("post_id");
|
||||
if (postId == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent replyIntent = new Intent(mContext, NotificationReplyBroadcastReceiver.class);
|
||||
replyIntent.setAction(KEY_TEXT_REPLY);
|
||||
replyIntent.putExtra(NOTIFICATION_ID, notificationId);
|
||||
replyIntent.putExtra("pushNotification", bundle);
|
||||
|
||||
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(
|
||||
mContext,
|
||||
notificationId,
|
||||
replyIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
|
||||
.setLabel("Reply")
|
||||
.build();
|
||||
|
||||
int icon = R.drawable.ic_notif_action_reply;
|
||||
CharSequence title = "Reply";
|
||||
Notification.Action replyAction = new Notification.Action.Builder(icon, title, replyPendingIntent)
|
||||
.addRemoteInput(remoteInput)
|
||||
.setAllowGeneratedReplies(true)
|
||||
.build();
|
||||
|
||||
notification
|
||||
.setShowWhen(true)
|
||||
.addAction(replyAction);
|
||||
}
|
||||
|
||||
private void notifyReceivedToJS() {
|
||||
|
|
@ -393,34 +469,51 @@ public class CustomPushNotification extends PushNotification {
|
|||
|
||||
private void cancelNotification(Bundle data, int notificationId) {
|
||||
final String channelId = data.getString("channel_id");
|
||||
CustomPushNotification.clearNotification(mContext.getApplicationContext(), notificationId, channelId);
|
||||
final String badge = data.getString("badge");
|
||||
|
||||
final String badgeString = data.getString("badge");
|
||||
CustomPushNotification.badgeCount = Integer.parseInt(badgeString);
|
||||
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), CustomPushNotification.badgeCount);
|
||||
CustomPushNotification.badgeCount = Integer.parseInt(badge);
|
||||
CustomPushNotification.clearNotification(mContext.getApplicationContext(), notificationId, channelId);
|
||||
}
|
||||
|
||||
private String getSenderName(String senderName, String channelName, String message) {
|
||||
private String getSenderName(Bundle bundle) {
|
||||
String senderName = bundle.getString("sender_name");
|
||||
if (senderName != null) {
|
||||
return senderName;
|
||||
} else if (channelName != null && channelName.startsWith("@")) {
|
||||
}
|
||||
|
||||
String channelName = bundle.getString("channel_name");
|
||||
if (channelName != null && channelName.startsWith("@")) {
|
||||
return channelName;
|
||||
}
|
||||
|
||||
String name = message.split(":")[0];
|
||||
if (name != message) {
|
||||
return name;
|
||||
String message = bundle.getString("message");
|
||||
if (message != null) {
|
||||
String name = message.split(":")[0];
|
||||
if (name != message) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return " ";
|
||||
}
|
||||
|
||||
private String removeSenderFromMessage(String senderName, String channelName, String message) {
|
||||
String sender = String.format("%s", getSenderName(senderName, channelName, message));
|
||||
return message.replaceFirst(sender, "").replaceFirst(": ", "").trim();
|
||||
private String removeSenderNameFromMessage(String message, String senderName) {
|
||||
return message.replaceFirst(senderName, "").replaceFirst(": ", "").trim();
|
||||
}
|
||||
|
||||
private void notificationReceiptDelivery(String ackId, String type) {
|
||||
ReceiptDelivery.send(context, ackId, type);
|
||||
}
|
||||
|
||||
private void createNotificationChannels() {
|
||||
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
mHighImportanceChannel = new NotificationChannel("channel_01", "High Importance", NotificationManager.IMPORTANCE_HIGH);
|
||||
mHighImportanceChannel.setShowBadge(true);
|
||||
notificationManager.createNotificationChannel(mHighImportanceChannel);
|
||||
|
||||
mMinImportanceChannel = new NotificationChannel("channel_02", "Min Importance", NotificationManager.IMPORTANCE_MIN);
|
||||
mMinImportanceChannel.setShowBadge(true);
|
||||
notificationManager.createNotificationChannel(mMinImportanceChannel);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.wix.reactnativenotifications.core.AppLaunchHelper;
|
|||
import com.wix.reactnativenotifications.core.notificationdrawer.PushNotificationsDrawer;
|
||||
import com.wix.reactnativenotifications.core.notificationdrawer.IPushNotificationsDrawer;
|
||||
import com.wix.reactnativenotifications.core.notificationdrawer.INotificationsDrawerApplication;
|
||||
import com.wix.reactnativenotifications.helpers.PushNotificationHelper;
|
||||
|
||||
import static com.wix.reactnativenotifications.Defs.LOGTAG;
|
||||
|
||||
public class CustomPushNotificationDrawer extends PushNotificationsDrawer {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ project(':react-native-cookies').projectDir = new File(rootProject.projectDir, '
|
|||
include ':react-native-vector-icons'
|
||||
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
||||
include ':reactnativenotifications'
|
||||
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android')
|
||||
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android/app')
|
||||
|
||||
include ':app'
|
||||
include ':react-native-svg'
|
||||
|
|
|
|||
|
|
@ -67,27 +67,6 @@ export default class ProfilePicture extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.mounted) {
|
||||
if (nextProps.edit && nextProps.imageUri && nextProps.imageUri !== this.props.imageUri) {
|
||||
this.setImageURL(nextProps.imageUri);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextProps.profileImageUri !== '' && nextProps.profileImageUri !== this.props.profileImageUri) {
|
||||
this.setImageURL(nextProps.profileImageUri);
|
||||
}
|
||||
|
||||
const url = this.props.user ? Client4.getProfilePictureUrl(this.props.user.id, this.props.user.last_picture_update) : null;
|
||||
const nextUrl = nextProps.user ? Client4.getProfilePictureUrl(nextProps.user.id, nextProps.user.last_picture_update) : null;
|
||||
|
||||
if (nextUrl && url !== nextUrl) {
|
||||
// empty function is so that promise unhandled is not triggered in dev mode
|
||||
ImageCacheManager.cache('', nextUrl, this.setImageURL).then(this.clearProfileImageUri).catch(emptyFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
|
@ -107,6 +86,23 @@ export default class ProfilePicture extends PureComponent {
|
|||
componentDidUpdate(prevProps) {
|
||||
if (this.props.profileImageRemove !== prevProps.profileImageRemove) {
|
||||
this.setImageURL(null);
|
||||
} else if (this.mounted) {
|
||||
if (this.props.edit && this.props.imageUri && this.props.imageUri !== prevProps.imageUri) {
|
||||
this.setImageURL(this.props.imageUri);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.profileImageUri !== '' && this.props.profileImageUri !== prevProps.profileImageUri) {
|
||||
this.setImageURL(this.props.profileImageUri);
|
||||
}
|
||||
|
||||
const url = prevProps.user ? Client4.getProfilePictureUrl(prevProps.user.id, prevProps.user.last_picture_update) : null;
|
||||
const nextUrl = this.props.user ? Client4.getProfilePictureUrl(this.props.user.id, this.props.user.last_picture_update) : null;
|
||||
|
||||
if (nextUrl && url !== nextUrl) {
|
||||
// empty function is so that promise unhandled is not triggered in dev mode
|
||||
ImageCacheManager.cache('', nextUrl, this.setImageURL).then(this.clearProfileImageUri).catch(emptyFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ export default class RetryBarIndicator extends PureComponent {
|
|||
retryMessageHeight: new Animated.Value(0),
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.failed !== nextProps.failed) {
|
||||
this.toggleRetryMessage(nextProps.failed);
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.failed !== prevProps.failed) {
|
||||
this.toggleRetryMessage(this.props.failed);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SafeAreaIos should match snapshot 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
"marginLeft": 0,
|
||||
"marginRight": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#1153ab",
|
||||
"paddingTop": 44,
|
||||
"zIndex": 10,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"height": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Dimensions, Keyboard, NativeModules, View} from 'react-native';
|
||||
import {Keyboard, NativeModules, View} from 'react-native';
|
||||
import SafeArea from 'react-native-safe-area';
|
||||
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
|
@ -57,17 +57,16 @@ export default class SafeAreaIos extends PureComponent {
|
|||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
|
||||
Dimensions.addEventListener('change', this.getSafeAreaInsets);
|
||||
SafeArea.addEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
EventEmitter.on('update_safe_area_view', this.getSafeAreaInsets);
|
||||
this.keyboardDidShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
|
||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
|
||||
|
||||
this.getSafeAreaInsets();
|
||||
this.getStatusBarHeight();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Dimensions.removeEventListener('change', this.getSafeAreaInsets);
|
||||
SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
EventEmitter.off('update_safe_area_view', this.getSafeAreaInsets);
|
||||
this.keyboardDidShowListener.remove();
|
||||
this.keyboardDidHideListener.remove();
|
||||
|
|
@ -103,6 +102,15 @@ export default class SafeAreaIos extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
onSafeAreaInsetsForRootViewChange = (result) => {
|
||||
const {safeAreaInsets} = result;
|
||||
|
||||
if (this.mounted && (DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets)) {
|
||||
this.getStatusBarHeight();
|
||||
this.setState({safeAreaInsets});
|
||||
}
|
||||
}
|
||||
|
||||
keyboardWillHide = () => {
|
||||
this.setState({keyboard: false});
|
||||
};
|
||||
|
|
|
|||
151
app/components/safe_area_view/safe_area_view.ios.test.js
Normal file
151
app/components/safe_area_view/safe_area_view.ios.test.js
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react';
|
||||
import SafeArea from 'react-native-safe-area';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
|
||||
import SafeAreaIos from './safe_area_view.ios';
|
||||
|
||||
describe('SafeAreaIos', () => {
|
||||
const baseProps = {
|
||||
children: [],
|
||||
keyboardOffset: 100,
|
||||
useLandscapeMargin: false,
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
const TEST_INSETS_1 = {
|
||||
safeAreaInsets: {
|
||||
top: 123,
|
||||
left: 123,
|
||||
bottom: 123,
|
||||
right: 123,
|
||||
},
|
||||
};
|
||||
|
||||
const TEST_INSETS_2 = {
|
||||
safeAreaInsets: {
|
||||
top: 456,
|
||||
left: 456,
|
||||
bottom: 456,
|
||||
right: 456,
|
||||
},
|
||||
};
|
||||
|
||||
SafeArea.getSafeAreaInsetsForRootView = jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve(TEST_INSETS_1);
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should get safe area insets on mount if DeviceTypes.IS_IPHONE_WITH_INSETS is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = true;
|
||||
mattermostManaged.hasSafeAreaInsets = false;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(SafeArea.getSafeAreaInsetsForRootView).toHaveBeenCalled();
|
||||
await SafeArea.getSafeAreaInsetsForRootView();
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(TEST_INSETS_1.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should get safe area insets on mount if mattermostManaged.hasSafeAreaInsets is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = false;
|
||||
mattermostManaged.hasSafeAreaInsets = true;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(SafeArea.getSafeAreaInsetsForRootView).toHaveBeenCalled();
|
||||
await SafeArea.getSafeAreaInsetsForRootView();
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(TEST_INSETS_1.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should not get safe area insets on mount if neither DeviceTypes.IS_IPHONE_WITH_INSET nor mattermostManaged.hasSafeAreaInsets is true', async () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = false;
|
||||
mattermostManaged.hasSafeAreaInsets = false;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(SafeArea.getSafeAreaInsetsForRootView).not.toHaveBeenCalled();
|
||||
await SafeArea.getSafeAreaInsetsForRootView();
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_1.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set safe area insets on change if mounted and DeviceTypes.IS_IPHONE_WITH_INSETS is true', () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = true;
|
||||
mattermostManaged.hasSafeAreaInsets = false;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_2);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set safe area insets on change if mounted and mattermostManaged.hasSafeAreaInsets is true', () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = false;
|
||||
mattermostManaged.hasSafeAreaInsets = true;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_2);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should not set safe area insets on change if mounted and neither DeviceTypes.IS_IPHONE_WITH_INSETS nor mattermostManaged.hasSafeAreaInsets is true', () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = false;
|
||||
mattermostManaged.hasSafeAreaInsets = false;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_2);
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set safe area insets on change not mounted', () => {
|
||||
DeviceTypes.IS_IPHONE_WITH_INSETS = true;
|
||||
mattermostManaged.hasSafeAreaInsets = true;
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.mounted = false;
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_2);
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
});
|
||||
|
|
@ -132,9 +132,9 @@ export default class Search extends Component {
|
|||
this.shadowHeight = this.props.shadowOffsetHeightCollapsed;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.value !== nextProps.value) {
|
||||
if (nextProps.value) {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.value !== prevProps.value) {
|
||||
if (this.props.value) {
|
||||
this.iconDeleteAnimated = new Animated.Value(1);
|
||||
} else {
|
||||
this.iconDeleteAnimated = new Animated.Value(0);
|
||||
|
|
|
|||
|
|
@ -94,9 +94,8 @@ class PushNotification {
|
|||
NotificationsAndroid.cancelAllLocalNotifications();
|
||||
}
|
||||
|
||||
setApplicationIconBadgeNumber(number) {
|
||||
const count = number < 0 ? 0 : number;
|
||||
NotificationsAndroid.setBadgesCount(count);
|
||||
setApplicationIconBadgeNumber() {
|
||||
// Not supported for Android
|
||||
}
|
||||
|
||||
getNotification() {
|
||||
|
|
@ -116,7 +115,6 @@ class PushNotification {
|
|||
}
|
||||
|
||||
clearNotifications = () => {
|
||||
this.setApplicationIconBadgeNumber(0);
|
||||
this.cancelAllLocalNotifications(); // TODO: Only cancel the local notifications that belong to this server
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,23 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {AppState} from 'react-native';
|
||||
import NotificationsIOS, {NotificationAction, NotificationCategory} from 'react-native-notifications';
|
||||
import NotificationsIOS, {
|
||||
NotificationAction,
|
||||
NotificationCategory,
|
||||
DEVICE_REMOTE_NOTIFICATIONS_REGISTERED_EVENT,
|
||||
DEVICE_NOTIFICATION_RECEIVED_FOREGROUND_EVENT,
|
||||
DEVICE_NOTIFICATION_OPENED_EVENT,
|
||||
} from 'react-native-notifications';
|
||||
|
||||
import {getBadgeCount} from 'app/selectors/views';
|
||||
import ephemeralStore from 'app/store/ephemeral_store';
|
||||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
import {getLocalizedMessage} from 'app/i18n';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
const CATEGORY = 'CAN_REPLY';
|
||||
const REPLY_ACTION = 'REPLY_ACTION';
|
||||
|
||||
let replyCategory;
|
||||
const replies = new Set();
|
||||
|
||||
class PushNotification {
|
||||
|
|
@ -21,24 +29,9 @@ class PushNotification {
|
|||
this.onReply = null;
|
||||
this.reduxStore = null;
|
||||
|
||||
NotificationsIOS.addEventListener('remoteNotificationsRegistered', this.onRemoteNotificationsRegistered);
|
||||
NotificationsIOS.addEventListener('notificationReceivedForeground', this.onNotificationReceivedForeground);
|
||||
NotificationsIOS.addEventListener('notificationReceivedBackground', this.onNotificationReceivedBackground);
|
||||
NotificationsIOS.addEventListener('notificationOpened', this.onNotificationOpened);
|
||||
|
||||
const replyAction = new NotificationAction({
|
||||
activationMode: 'background',
|
||||
title: 'Reply',
|
||||
behavior: 'textInput',
|
||||
authenticationRequired: true,
|
||||
identifier: REPLY_ACTION,
|
||||
}, this.handleReply);
|
||||
|
||||
replyCategory = new NotificationCategory({
|
||||
identifier: CATEGORY,
|
||||
actions: [replyAction],
|
||||
context: 'default',
|
||||
});
|
||||
NotificationsIOS.addEventListener(DEVICE_REMOTE_NOTIFICATIONS_REGISTERED_EVENT, this.onRemoteNotificationsRegistered);
|
||||
NotificationsIOS.addEventListener(DEVICE_NOTIFICATION_RECEIVED_FOREGROUND_EVENT, this.onNotificationReceivedForeground);
|
||||
NotificationsIOS.addEventListener(DEVICE_NOTIFICATION_OPENED_EVENT, this.onNotificationOpened);
|
||||
}
|
||||
|
||||
handleNotification = (data, foreground, userInteraction) => {
|
||||
|
|
@ -55,18 +48,14 @@ class PushNotification {
|
|||
}
|
||||
};
|
||||
|
||||
handleReply = (action, completed) => {
|
||||
if (action.identifier === REPLY_ACTION) {
|
||||
const data = action.notification.getData();
|
||||
const text = action.text;
|
||||
const badge = parseInt(action.notification._badge, 10) - 1; //eslint-disable-line no-underscore-dangle
|
||||
handleReply = (notification, text, completion) => {
|
||||
const data = notification.getData();
|
||||
|
||||
if (this.onReply && !replies.has(action.completionKey)) {
|
||||
replies.add(action.completionKey);
|
||||
this.onReply(data, text, badge, completed);
|
||||
}
|
||||
if (this.onReply && !replies.has(data.identifier)) {
|
||||
replies.add(data.identifier);
|
||||
this.onReply(data, text, completion);
|
||||
} else {
|
||||
completed();
|
||||
completion();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -76,9 +65,39 @@ class PushNotification {
|
|||
this.onNotification = options.onNotification;
|
||||
this.onReply = options.onReply;
|
||||
|
||||
this.requestPermissions([replyCategory]);
|
||||
this.requestNotificationReplyPermissions();
|
||||
}
|
||||
|
||||
NotificationsIOS.consumeBackgroundQueue();
|
||||
requestNotificationReplyPermissions = () => {
|
||||
const replyCategory = this.createReplyCategory();
|
||||
this.requestPermissions([replyCategory]);
|
||||
}
|
||||
|
||||
createReplyCategory = () => {
|
||||
const {getState} = this.reduxStore;
|
||||
const state = getState();
|
||||
const locale = getCurrentLocale(state);
|
||||
|
||||
const replyTitle = getLocalizedMessage(locale, t('mobile.push_notification_reply.title'));
|
||||
const replyButton = getLocalizedMessage(locale, t('mobile.push_notification_reply.button'));
|
||||
const replyPlaceholder = getLocalizedMessage(locale, t('mobile.push_notification_reply.placeholder'));
|
||||
|
||||
const replyAction = new NotificationAction({
|
||||
activationMode: 'background',
|
||||
title: replyTitle,
|
||||
textInput: {
|
||||
buttonTitle: replyButton,
|
||||
placeholder: replyPlaceholder,
|
||||
},
|
||||
authenticationRequired: true,
|
||||
identifier: REPLY_ACTION,
|
||||
});
|
||||
|
||||
return new NotificationCategory({
|
||||
identifier: CATEGORY,
|
||||
actions: [replyAction],
|
||||
context: 'default',
|
||||
});
|
||||
}
|
||||
|
||||
requestPermissions = (permissions) => {
|
||||
|
|
@ -89,7 +108,7 @@ class PushNotification {
|
|||
if (notification.date) {
|
||||
const deviceNotification = {
|
||||
fireDate: notification.date.toISOString(),
|
||||
alertBody: notification.message,
|
||||
body: notification.message,
|
||||
alertAction: '',
|
||||
userInfo: notification.userInfo,
|
||||
};
|
||||
|
|
@ -100,7 +119,7 @@ class PushNotification {
|
|||
|
||||
localNotification(notification) {
|
||||
this.deviceNotification = {
|
||||
alertBody: notification.message,
|
||||
body: notification.message,
|
||||
alertAction: '',
|
||||
userInfo: notification.userInfo,
|
||||
};
|
||||
|
|
@ -138,12 +157,17 @@ class PushNotification {
|
|||
this.handleNotification(info, true, false);
|
||||
};
|
||||
|
||||
onNotificationOpened = (notification) => {
|
||||
const info = {
|
||||
...notification.getData(),
|
||||
message: notification.getMessage(),
|
||||
};
|
||||
this.handleNotification(info, false, true);
|
||||
onNotificationOpened = (notification, completion, action) => {
|
||||
if (action.identifier === REPLY_ACTION) {
|
||||
this.handleReply(notification, action.text, completion);
|
||||
} else {
|
||||
const info = {
|
||||
...notification.getData(),
|
||||
message: notification.getMessage(),
|
||||
};
|
||||
this.handleNotification(info, false, true);
|
||||
completion();
|
||||
}
|
||||
};
|
||||
|
||||
onRemoteNotificationsRegistered = (deviceToken) => {
|
||||
|
|
@ -165,6 +189,10 @@ class PushNotification {
|
|||
this.deviceNotification = null;
|
||||
}
|
||||
|
||||
getDeliveredNotifications(callback) {
|
||||
NotificationsIOS.getDeliveredNotifications(callback);
|
||||
}
|
||||
|
||||
clearChannelNotifications(channelId) {
|
||||
NotificationsIOS.getDeliveredNotifications((notifications) => {
|
||||
const ids = [];
|
||||
|
|
@ -180,7 +208,7 @@ class PushNotification {
|
|||
for (let i = 0; i < notifications.length; i++) {
|
||||
const notification = notifications[i];
|
||||
|
||||
if (notification.userInfo.channel_id === channelId) {
|
||||
if (notification.channel_id === channelId) {
|
||||
ids.push(notification.identifier);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,25 +39,25 @@ describe('PushNotification', () => {
|
|||
// Three channel1 delivered notifications
|
||||
{
|
||||
identifier: 'channel1-1',
|
||||
userInfo: {channel_id: channel1ID},
|
||||
channel_id: channel1ID,
|
||||
},
|
||||
{
|
||||
identifier: 'channel1-2',
|
||||
userInfo: {channel_id: channel1ID},
|
||||
channel_id: channel1ID,
|
||||
},
|
||||
{
|
||||
identifier: 'channel1-3',
|
||||
userInfo: {channel_id: channel1ID},
|
||||
channel_id: channel1ID,
|
||||
},
|
||||
|
||||
// Two channel2 delivered notifications
|
||||
{
|
||||
identifier: 'channel2-1',
|
||||
userInfo: {channel_id: channel2ID},
|
||||
channel_id: channel2ID,
|
||||
},
|
||||
{
|
||||
identifier: 'channel2-2',
|
||||
userInfo: {channel_id: channel2ID},
|
||||
channel_id: channel2ID,
|
||||
},
|
||||
];
|
||||
NotificationsIOS.setDeliveredNotifications(deliveredNotifications);
|
||||
|
|
@ -73,8 +73,8 @@ describe('PushNotification', () => {
|
|||
|
||||
await NotificationsIOS.getDeliveredNotifications(async (deliveredNotifs) => {
|
||||
expect(deliveredNotifs.length).toBe(2);
|
||||
const channel1DeliveredNotifications = deliveredNotifs.filter((n) => n.userInfo.channel_id === channel1ID);
|
||||
const channel2DeliveredNotifications = deliveredNotifs.filter((n) => n.userInfo.channel_id === channel2ID);
|
||||
const channel1DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel1ID);
|
||||
const channel2DeliveredNotifications = deliveredNotifs.filter((n) => n.channel_id === channel2ID);
|
||||
expect(channel1DeliveredNotifications.length).toBe(0);
|
||||
expect(channel2DeliveredNotifications.length).toBe(2);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import SafeAreaView from 'app/components/safe_area_view';
|
|||
import SettingsSidebar from 'app/components/sidebars/settings';
|
||||
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {setNavigatorStyles} from 'app/utils/theme';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import tracker from 'app/utils/time_tracker';
|
||||
|
|
@ -27,7 +28,6 @@ import telemetry from 'app/telemetry';
|
|||
import {
|
||||
goToScreen,
|
||||
showModalOverCurrentContext,
|
||||
mergeNavigationOptions,
|
||||
} from 'app/actions/navigation';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
|
|
@ -69,12 +69,7 @@ export default class ChannelBase extends PureComponent {
|
|||
this.postTextbox = React.createRef();
|
||||
this.keyboardTracker = React.createRef();
|
||||
|
||||
const options = {
|
||||
layout: {
|
||||
backgroundColor: props.theme.centerChannelBg,
|
||||
},
|
||||
};
|
||||
mergeNavigationOptions(props.componentId, options);
|
||||
setNavigatorStyles(props.componentId, props.theme);
|
||||
|
||||
if (LocalConfig.EnableMobileClientUpgrade && !ClientUpgradeListener) {
|
||||
ClientUpgradeListener = require('app/components/client_upgrade_listener').default;
|
||||
|
|
@ -113,12 +108,9 @@ export default class ChannelBase extends PureComponent {
|
|||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.theme !== nextProps.theme) {
|
||||
const options = {
|
||||
layout: {
|
||||
backgroundColor: nextProps.theme.centerChannelBg,
|
||||
},
|
||||
};
|
||||
mergeNavigationOptions(this.props.componentId, options);
|
||||
EphemeralStore.allNavigationComponentIds.forEach((componentId) => {
|
||||
setNavigatorStyles(componentId, nextProps.theme);
|
||||
});
|
||||
}
|
||||
|
||||
if (nextProps.currentTeamId && this.props.currentTeamId !== nextProps.currentTeamId) {
|
||||
|
|
@ -322,6 +314,12 @@ export default class ChannelBase extends PureComponent {
|
|||
</MainSidebar>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
// Overriden in channel.android.js and channel.ios.js
|
||||
// but defined here for channel_base.test.js
|
||||
return; // eslint-disable-line no-useless-return
|
||||
}
|
||||
}
|
||||
|
||||
export const style = StyleSheet.create({
|
||||
|
|
|
|||
77
app/screens/channel/channel_base.test.js
Normal file
77
app/screens/channel/channel_base.test.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import * as NavigationActions from 'app/actions/navigation';
|
||||
|
||||
import ChannelBase from './channel_base';
|
||||
|
||||
jest.mock('react-intl');
|
||||
|
||||
describe('ChannelBase', () => {
|
||||
const componentIds = ['component-1', 'component-2', 'component-3'];
|
||||
const baseProps = {
|
||||
actions: {
|
||||
loadChannelsIfNecessary: jest.fn(),
|
||||
loadProfilesAndTeamMembersForDMSidebar: jest.fn(),
|
||||
selectDefaultTeam: jest.fn(),
|
||||
selectInitialChannel: jest.fn(),
|
||||
recordLoadTime: jest.fn(),
|
||||
getChannelStats: jest.fn(),
|
||||
},
|
||||
componentId: componentIds[0],
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
const optionsForTheme = (theme) => {
|
||||
return {
|
||||
topBar: {
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
leftButtonColor: theme.sidebarHeaderTextColor,
|
||||
rightButtonColor: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
test('should call mergeNavigationOptions on all navigation components when theme changes', () => {
|
||||
const mergeNavigationOptions = jest.spyOn(NavigationActions, 'mergeNavigationOptions');
|
||||
|
||||
componentIds.forEach((componentId) => {
|
||||
EphemeralStore.addNavigationComponentId(componentId);
|
||||
});
|
||||
|
||||
const wrapper = shallow(
|
||||
<ChannelBase {...baseProps}/>,
|
||||
);
|
||||
|
||||
const themeOptions = optionsForTheme(Preferences.THEMES.default);
|
||||
expect(mergeNavigationOptions.mock.calls).toEqual([
|
||||
[componentIds[0], themeOptions],
|
||||
]);
|
||||
mergeNavigationOptions.mockClear();
|
||||
|
||||
wrapper.setProps({theme: Preferences.THEMES.mattermostDark});
|
||||
|
||||
const newThemeOptions = optionsForTheme(Preferences.THEMES.mattermostDark);
|
||||
expect(mergeNavigationOptions.mock.calls).toEqual([
|
||||
[componentIds[2], newThemeOptions],
|
||||
[componentIds[1], newThemeOptions],
|
||||
[componentIds[0], newThemeOptions],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
exports[`ChannelDrawerButton should match, full snapshot 1`] = `
|
||||
<TouchableOpacity
|
||||
accessibilityHint="Opens the channels and teams drawer"
|
||||
accessibilityLabel="Channels and teams"
|
||||
accessibilityRole="button"
|
||||
accessible={true}
|
||||
activeOpacity={0.2}
|
||||
onPress={[Function]}
|
||||
style={
|
||||
|
|
@ -38,6 +42,10 @@ exports[`ChannelDrawerButton should match, full snapshot 1`] = `
|
|||
|
||||
exports[`ChannelDrawerButton should match, full snapshot 2`] = `
|
||||
<TouchableOpacity
|
||||
accessibilityHint="Opens the channels and teams drawer"
|
||||
accessibilityLabel="Channels and teams"
|
||||
accessibilityRole="button"
|
||||
accessible={true}
|
||||
activeOpacity={0.2}
|
||||
onPress={[Function]}
|
||||
style={
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import Badge from 'app/components/badge';
|
|||
import PushNotifications from 'app/push_notifications';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import telemetry from 'app/telemetry';
|
||||
|
||||
|
|
@ -25,6 +27,10 @@ export default class ChannelDrawerButton extends PureComponent {
|
|||
visible: PropTypes.bool,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
badgeCount: 0,
|
||||
currentChannel: {},
|
||||
|
|
@ -61,6 +67,22 @@ export default class ChannelDrawerButton extends PureComponent {
|
|||
visible,
|
||||
} = this.props;
|
||||
|
||||
const {formatMessage} = this.context.intl;
|
||||
|
||||
const buttonDescriptor = {
|
||||
id: t('navbar.channel_drawer.button'),
|
||||
defaultMessage: 'Channels and teams',
|
||||
description: 'Accessibility helper for channel drawer button.',
|
||||
};
|
||||
const accessibilityLabel = formatMessage(buttonDescriptor);
|
||||
|
||||
const buttonHint = {
|
||||
id: t('navbar.channel_drawer.hint'),
|
||||
defaultMessage: 'Opens the channels and teams drawer',
|
||||
description: 'Accessibility helper for explaining what the channel drawer button will do.',
|
||||
};
|
||||
const accessibilityHint = formatMessage(buttonHint);
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
let badge;
|
||||
|
|
@ -95,6 +117,10 @@ export default class ChannelDrawerButton extends PureComponent {
|
|||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
accessible={true}
|
||||
accessibilityHint={accessibilityHint}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole='button'
|
||||
onPress={this.handlePress}
|
||||
style={containerStyle}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import NotificationsIOS from 'react-native-notifications';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import Badge from 'app/components/badge';
|
||||
import PushNotification from 'app/push_notifications/push_notifications.ios';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import ChannelDrawerButton from './channel_drawer_button';
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ describe('ChannelDrawerButton', () => {
|
|||
afterEach(() => NotificationsIOS.setBadgesCount(0));
|
||||
|
||||
test('should match, full snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<ChannelDrawerButton {...baseProps}/>
|
||||
);
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ describe('ChannelDrawerButton', () => {
|
|||
badgeCount: 0,
|
||||
};
|
||||
|
||||
shallow(
|
||||
shallowWithIntl(
|
||||
<ChannelDrawerButton {...props}/>
|
||||
);
|
||||
expect(setApplicationIconBadgeNumber).not.toBeCalled();
|
||||
|
|
@ -83,7 +83,7 @@ describe('ChannelDrawerButton', () => {
|
|||
badgeCount: 1,
|
||||
};
|
||||
|
||||
shallow(
|
||||
shallowWithIntl(
|
||||
<ChannelDrawerButton {...props}/>
|
||||
);
|
||||
expect(setApplicationIconBadgeNumber).toHaveBeenCalledTimes(1);
|
||||
|
|
@ -97,7 +97,7 @@ describe('ChannelDrawerButton', () => {
|
|||
badgeCount: 0,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<ChannelDrawerButton {...props}/>
|
||||
);
|
||||
NotificationsIOS.getBadgesCount((count) => expect(count).toBe(0));
|
||||
|
|
@ -115,7 +115,7 @@ describe('ChannelDrawerButton', () => {
|
|||
badgeCount: 0,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
const wrapper = shallowWithIntl(
|
||||
<ChannelDrawerButton {...props}/>
|
||||
);
|
||||
wrapper.setProps({badgeCount: 2});
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ import {isLandscape} from 'app/selectors/device';
|
|||
import {
|
||||
handleSelectChannel,
|
||||
loadThreadIfNecessary,
|
||||
setChannelDisplayName,
|
||||
setChannelLoading,
|
||||
} from 'app/actions/views/channel';
|
||||
import {handleTeamChange} from 'app/actions/views/select_team';
|
||||
|
||||
|
|
@ -73,8 +71,6 @@ function mapDispatchToProps(dispatch) {
|
|||
joinChannel,
|
||||
loadThreadIfNecessary,
|
||||
selectPost,
|
||||
setChannelDisplayName,
|
||||
setChannelLoading,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,8 +65,6 @@ export default class Permalink extends PureComponent {
|
|||
joinChannel: PropTypes.func.isRequired,
|
||||
loadThreadIfNecessary: PropTypes.func.isRequired,
|
||||
selectPost: PropTypes.func.isRequired,
|
||||
setChannelDisplayName: PropTypes.func.isRequired,
|
||||
setChannelLoading: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
channelId: PropTypes.string,
|
||||
channelIsArchived: PropTypes.bool,
|
||||
|
|
@ -221,24 +219,22 @@ export default class Permalink extends PureComponent {
|
|||
};
|
||||
|
||||
handlePress = () => {
|
||||
const {channelIdState, channelNameState} = this.state;
|
||||
const {channelIdState} = this.state;
|
||||
|
||||
if (this.refs.view) {
|
||||
this.refs.view.growOut().then(() => {
|
||||
this.jumpToChannel(channelIdState, channelNameState);
|
||||
this.jumpToChannel(channelIdState);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
jumpToChannel = async (channelId, channelDisplayName) => {
|
||||
jumpToChannel = async (channelId) => {
|
||||
if (channelId) {
|
||||
const {actions, channelTeamId, currentTeamId, onClose} = this.props;
|
||||
const currentChannelId = this.props.channelId;
|
||||
const {
|
||||
handleSelectChannel,
|
||||
handleTeamChange,
|
||||
setChannelLoading,
|
||||
setChannelDisplayName,
|
||||
} = actions;
|
||||
|
||||
actions.selectPost('');
|
||||
|
|
@ -263,8 +259,6 @@ export default class Permalink extends PureComponent {
|
|||
handleTeamChange(channelTeamId);
|
||||
}
|
||||
|
||||
setChannelLoading(channelId !== currentChannelId);
|
||||
setChannelDisplayName(channelDisplayName);
|
||||
handleSelectChannel(channelId);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ describe('Permalink', () => {
|
|||
joinChannel: jest.fn(),
|
||||
loadThreadIfNecessary: jest.fn(),
|
||||
selectPost: jest.fn(),
|
||||
setChannelDisplayName: jest.fn(),
|
||||
setChannelLoading: jest.fn(),
|
||||
};
|
||||
|
||||
const baseProps = {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ export default class NotificationSettingsAutoResponder extends PureComponent {
|
|||
const {intl} = this.context;
|
||||
const notifyProps = getNotificationProps(currentUser);
|
||||
|
||||
this.autoresponderRef = React.createRef();
|
||||
|
||||
const autoResponderDefault = intl.formatMessage({
|
||||
id: 'mobile.notification_settings.auto_responder.default_message',
|
||||
defaultMessage: 'Hello, I am out of office and unable to respond to messages.',
|
||||
|
|
@ -65,6 +67,16 @@ export default class NotificationSettingsAutoResponder extends PureComponent {
|
|||
this.saveUserNotifyProps();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (this.autoresponderRef.current) {
|
||||
this.autoresponderRef.current.focus();
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
saveUserNotifyProps = () => {
|
||||
this.props.onBack({
|
||||
...this.state,
|
||||
|
|
@ -126,8 +138,7 @@ export default class NotificationSettingsAutoResponder extends PureComponent {
|
|||
>
|
||||
<View style={style.inputContainer}>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
autoFocus={true}
|
||||
ref={this.keywordsRef}
|
||||
ref={this.autoresponderRef}
|
||||
value={autoResponderMessage}
|
||||
blurOnSubmit={false}
|
||||
onChangeText={this.onAutoResponseChangeText}
|
||||
|
|
|
|||
|
|
@ -14,116 +14,10 @@ import ThemeTile from './theme_tile';
|
|||
|
||||
jest.mock('react-intl');
|
||||
|
||||
const allowedThemes = [
|
||||
{
|
||||
type: 'Mattermost',
|
||||
sidebarBg: '#145dbf',
|
||||
sidebarText: '#ffffff',
|
||||
sidebarUnreadText: '#ffffff',
|
||||
sidebarTextHoverBg: '#4578bf',
|
||||
sidebarTextActiveBorder: '#579eff',
|
||||
sidebarTextActiveColor: '#ffffff',
|
||||
sidebarHeaderBg: '#1153ab',
|
||||
sidebarHeaderTextColor: '#ffffff',
|
||||
onlineIndicator: '#06d6a0',
|
||||
awayIndicator: '#ffbc42',
|
||||
dndIndicator: '#f74343',
|
||||
mentionBg: '#ffffff',
|
||||
mentionColor: '#145dbf',
|
||||
centerChannelBg: '#ffffff',
|
||||
centerChannelColor: '#3d3c40',
|
||||
newMessageSeparator: '#ff8800',
|
||||
linkColor: '#2389d7',
|
||||
buttonBg: '#166de0',
|
||||
buttonColor: '#ffffff',
|
||||
errorTextColor: '#fd5960',
|
||||
mentionHighlightBg: '#ffe577',
|
||||
mentionHighlightLink: '#166de0',
|
||||
codeTheme: 'github',
|
||||
key: 'default',
|
||||
},
|
||||
{
|
||||
type: 'Organization',
|
||||
sidebarBg: '#2071a7',
|
||||
sidebarText: '#ffffff',
|
||||
sidebarUnreadText: '#ffffff',
|
||||
sidebarTextHoverBg: '#136197',
|
||||
sidebarTextActiveBorder: '#7ab0d6',
|
||||
sidebarTextActiveColor: '#ffffff',
|
||||
sidebarHeaderBg: '#2f81b7',
|
||||
sidebarHeaderTextColor: '#ffffff',
|
||||
onlineIndicator: '#7dbe00',
|
||||
awayIndicator: '#dcbd4e',
|
||||
dndIndicator: '#ff6a6a',
|
||||
mentionBg: '#fbfbfb',
|
||||
mentionColor: '#2071f7',
|
||||
centerChannelBg: '#f2f4f8',
|
||||
centerChannelColor: '#333333',
|
||||
newMessageSeparator: '#ff8800',
|
||||
linkColor: '#2f81b7',
|
||||
buttonBg: '#1dacfc',
|
||||
buttonColor: '#ffffff',
|
||||
errorTextColor: '#a94442',
|
||||
mentionHighlightBg: '#f3e197',
|
||||
mentionHighlightLink: '#2f81b7',
|
||||
codeTheme: 'github',
|
||||
key: 'organization',
|
||||
},
|
||||
{
|
||||
type: 'Mattermost Dark',
|
||||
sidebarBg: '#1b2c3e',
|
||||
sidebarText: '#ffffff',
|
||||
sidebarUnreadText: '#ffffff',
|
||||
sidebarTextHoverBg: '#4a5664',
|
||||
sidebarTextActiveBorder: '#66b9a7',
|
||||
sidebarTextActiveColor: '#ffffff',
|
||||
sidebarHeaderBg: '#1b2c3e',
|
||||
sidebarHeaderTextColor: '#ffffff',
|
||||
onlineIndicator: '#65dcc8',
|
||||
awayIndicator: '#c1b966',
|
||||
dndIndicator: '#e81023',
|
||||
mentionBg: '#b74a4a',
|
||||
mentionColor: '#ffffff',
|
||||
centerChannelBg: '#2f3e4e',
|
||||
centerChannelColor: '#dddddd',
|
||||
newMessageSeparator: '#5de5da',
|
||||
linkColor: '#a4ffeb',
|
||||
buttonBg: '#4cbba4',
|
||||
buttonColor: '#ffffff',
|
||||
errorTextColor: '#ff6461',
|
||||
mentionHighlightBg: '#984063',
|
||||
mentionHighlightLink: '#a4ffeb',
|
||||
codeTheme: 'solarized-dark',
|
||||
key: 'mattermostDark',
|
||||
},
|
||||
{
|
||||
type: 'Windows Dark',
|
||||
sidebarBg: '#171717',
|
||||
sidebarText: '#ffffff',
|
||||
sidebarUnreadText: '#ffffff',
|
||||
sidebarTextHoverBg: '#302e30',
|
||||
sidebarTextActiveBorder: '#196caf',
|
||||
sidebarTextActiveColor: '#ffffff',
|
||||
sidebarHeaderBg: '#1f1f1f',
|
||||
sidebarHeaderTextColor: '#ffffff',
|
||||
onlineIndicator: '#399fff',
|
||||
awayIndicator: '#c1b966',
|
||||
dndIndicator: '#e81023',
|
||||
mentionBg: '#0177e7',
|
||||
mentionColor: '#ffffff',
|
||||
centerChannelBg: '#1f1f1f',
|
||||
centerChannelColor: '#dddddd',
|
||||
newMessageSeparator: '#cc992d',
|
||||
linkColor: '#0d93ff',
|
||||
buttonBg: '#0177e7',
|
||||
buttonColor: '#ffffff',
|
||||
errorTextColor: '#ff6461',
|
||||
mentionHighlightBg: '#784098',
|
||||
mentionHighlightLink: '#a4ffeb',
|
||||
codeTheme: 'monokai',
|
||||
key: 'windows10',
|
||||
},
|
||||
];
|
||||
const allowedThemes = Object.keys(Preferences.THEMES).map((key) => ({
|
||||
key,
|
||||
...Preferences.THEMES[key],
|
||||
}));
|
||||
|
||||
describe('Theme', () => {
|
||||
const baseProps = {
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class PushNotificationUtils {
|
|||
}
|
||||
};
|
||||
|
||||
onPushNotificationReply = async (data, text, badge, completed) => {
|
||||
onPushNotificationReply = async (data, text, completion) => {
|
||||
const {dispatch, getState} = this.store;
|
||||
const state = getState();
|
||||
const credentials = await getAppCredentials(); // TODO Change to handle multiple servers
|
||||
|
|
@ -144,23 +144,21 @@ class PushNotificationUtils {
|
|||
channel_id: data.channel_id,
|
||||
},
|
||||
});
|
||||
completed();
|
||||
completion();
|
||||
return;
|
||||
}
|
||||
|
||||
if (badge >= 0) {
|
||||
PushNotifications.setApplicationIconBadgeNumber(badge);
|
||||
}
|
||||
|
||||
dispatch(markChannelViewedAndRead(data.channel_id));
|
||||
this.replyNotificationData = null;
|
||||
completed();
|
||||
|
||||
PushNotifications.getDeliveredNotifications((notifications) => {
|
||||
PushNotifications.setApplicationIconBadgeNumber(notifications.length);
|
||||
completion();
|
||||
});
|
||||
} else {
|
||||
this.replyNotificationData = {
|
||||
data,
|
||||
text,
|
||||
badge,
|
||||
completed,
|
||||
completion,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ jest.mock('react-native-notifications', () => {
|
|||
addEventListener: jest.fn(),
|
||||
NotificationAction: jest.fn(),
|
||||
NotificationCategory: jest.fn(),
|
||||
consumeBackgroundQueue: jest.fn(),
|
||||
localNotification: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
|
@ -49,9 +48,8 @@ describe('PushNotifications', () => {
|
|||
const channelID = 'channel-id';
|
||||
const data = {channel_id: channelID};
|
||||
const text = 'text';
|
||||
const badge = 1;
|
||||
const completed = () => {};
|
||||
await PushNotificationUtils.onPushNotificationReply(data, text, badge, completed);
|
||||
const completion = () => {};
|
||||
await PushNotificationUtils.onPushNotificationReply(data, text, completion);
|
||||
|
||||
const storeActions = store.getActions();
|
||||
const receivedPost = storeActions.some((action) => action.type === PostTypes.RECEIVED_POST);
|
||||
|
|
|
|||
|
|
@ -384,6 +384,9 @@
|
|||
"mobile.post.retry": "Refresh",
|
||||
"mobile.posts_view.moreMsg": "More New Messages Above",
|
||||
"mobile.privacy_link": "Privacy Policy",
|
||||
"mobile.push_notification_reply.button": "Send",
|
||||
"mobile.push_notification_reply.placeholder": "Write a reply...",
|
||||
"mobile.push_notification_reply.title": "Reply",
|
||||
"mobile.reaction_header.all_emojis": "All",
|
||||
"mobile.recent_mentions.empty_description": "Messages containing your username and other words that trigger mentions will appear here.",
|
||||
"mobile.recent_mentions.empty_title": "No Recent Mentions",
|
||||
|
|
@ -487,6 +490,8 @@
|
|||
"msg_typing.areTyping": "{users} and {last} are typing...",
|
||||
"msg_typing.isTyping": "{user} is typing...",
|
||||
"navbar_dropdown.logout": "Logout",
|
||||
"navbar.channel_drawer.button": "Channels and teams",
|
||||
"navbar.channel_drawer.hint": "Opens the channels and teams drawer",
|
||||
"navbar.leave": "Leave Channel",
|
||||
"password_form.title": "Password Reset",
|
||||
"password_send.checkInbox": "Please check your inbox.",
|
||||
|
|
|
|||
|
|
@ -11,69 +11,73 @@ configured = false
|
|||
is_build_pr = false
|
||||
|
||||
# Executes before anything else use to setup the script
|
||||
before_all do |lane, options|
|
||||
if lane.to_s == 'build_pr'
|
||||
pr = options[:pr]
|
||||
UI.success("Building #{pr}")
|
||||
ENV['BRANCH_TO_BUILD'] = pr
|
||||
is_build_pr = true
|
||||
before_all do
|
||||
is_build_pr = ENV['BUILD_PR'] == 'true'
|
||||
if is_build_pr && ENV['CIRCLECI'] == 'true'
|
||||
ENV['BRANCH_TO_BUILD'] = ENV['CIRCLE_BRANCH']
|
||||
end
|
||||
|
||||
# Raises an error is git is not clean
|
||||
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
|
||||
ensure_git_status_clean
|
||||
end
|
||||
UI.success("Building for release #{ENV['BUILD_FOR_RELEASE']}")
|
||||
|
||||
# Block to ensure we are on the right branch
|
||||
branch = ENV['BRANCH_TO_BUILD'] || ENV['GIT_BRANCH']
|
||||
begin
|
||||
ensure_git_branch(
|
||||
branch: branch
|
||||
)
|
||||
rescue
|
||||
sh "git checkout #{branch}"
|
||||
UI.success("Using branch \"#{branch}\"")
|
||||
end
|
||||
if ENV['CIRCLECI'] != 'true'
|
||||
# Raises an error is git is not clean
|
||||
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
|
||||
ensure_git_status_clean
|
||||
end
|
||||
|
||||
# If we are going to commit changes to git create a separate branch
|
||||
# so no changes are done in the branch that is being built
|
||||
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
|
||||
local_branch = ENV['GIT_LOCAL_BRANCH'] || 'build'
|
||||
sh "git checkout -b #{local_branch}"
|
||||
UI.success("Creating branch \"#{local_branch}\"")
|
||||
# Block to ensure we are on the right branch
|
||||
branch = ENV['BRANCH_TO_BUILD'] || ENV['GIT_BRANCH']
|
||||
begin
|
||||
ensure_git_branch(
|
||||
branch: branch
|
||||
)
|
||||
rescue
|
||||
sh "git checkout #{branch}"
|
||||
UI.success("Using branch \"#{branch}\"")
|
||||
end
|
||||
|
||||
# If we are going to commit changes to git create a separate branch
|
||||
# so no changes are done in the branch that is being built
|
||||
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
|
||||
local_branch = ENV['GIT_LOCAL_BRANCH'] || 'build'
|
||||
sh "git checkout -b #{local_branch}"
|
||||
UI.success("Creating branch \"#{local_branch}\"")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after_all do |lane|
|
||||
if lane.to_s == 'build'
|
||||
submit_to_store
|
||||
end
|
||||
|
||||
if ENV['RESET_GIT_BRANCH'] == 'true'
|
||||
branch = ENV['BRANCH_TO_BUILD'] || 'master'
|
||||
package_id = ENV['MAIN_APP_IDENTIFIER'] || 'com.mattermost.rnbeta'
|
||||
beta_dir = '../android/app/src/main/java/com/mattermost/rnbeta'
|
||||
release_dir = "../android/app/src/main/java/#{package_id.gsub '.', '/'}"
|
||||
|
||||
if beta_dir != release_dir
|
||||
sh "rm -rf #{release_dir}"
|
||||
if ENV['CIRCLECI'] != 'true'
|
||||
if lane.to_s == 'build'
|
||||
submit_to_store
|
||||
end
|
||||
|
||||
reset_git_repo(
|
||||
force: true,
|
||||
skip_clean: true
|
||||
)
|
||||
sh "git checkout #{branch}"
|
||||
if ENV['RESET_GIT_BRANCH'] == 'true'
|
||||
branch = ENV['BRANCH_TO_BUILD'] || 'master'
|
||||
package_id = ENV['MAIN_APP_IDENTIFIER'] || 'com.mattermost.rnbeta'
|
||||
beta_dir = '../android/app/src/main/java/com/mattermost/rnbeta'
|
||||
release_dir = "../android/app/src/main/java/#{package_id.gsub '.', '/'}"
|
||||
|
||||
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
|
||||
local_branch = ENV['GIT_LOCAL_BRANCH'] || 'build'
|
||||
sh "git branch -D #{local_branch}"
|
||||
UI.success("Deleted working branch \"#{local_branch}\"")
|
||||
if lane.to_s == 'build_pr'
|
||||
sh 'git checkout master'
|
||||
## Remove the branch for the PR
|
||||
sh "git branch -D #{branch}"
|
||||
UI.success("Deleted PR branch \"#{branch}\"")
|
||||
if beta_dir != release_dir
|
||||
sh "rm -rf #{release_dir}"
|
||||
end
|
||||
|
||||
reset_git_repo(
|
||||
force: true,
|
||||
skip_clean: true
|
||||
)
|
||||
sh "git checkout #{branch}"
|
||||
|
||||
if ENV['COMMIT_CHANGES_TO_GIT'] == 'true'
|
||||
local_branch = ENV['GIT_LOCAL_BRANCH'] || 'build'
|
||||
sh "git branch -D #{local_branch}"
|
||||
UI.success("Deleted working branch \"#{local_branch}\"")
|
||||
if lane.to_s == 'build_pr'
|
||||
sh 'git checkout master'
|
||||
## Remove the branch for the PR
|
||||
sh "git branch -D #{branch}"
|
||||
UI.success("Deleted PR branch \"#{branch}\"")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -196,20 +200,110 @@ lane :unsigned do
|
|||
unsigned
|
||||
end
|
||||
|
||||
desc 'Build the app using a PR so QA can test'
|
||||
lane :build_pr do
|
||||
configure
|
||||
desc 'Upload file to s3'
|
||||
lane :upload_file_to_s3 do |options|
|
||||
os_type = options[:os_type]
|
||||
file = options[:file]
|
||||
file_plist = ""
|
||||
|
||||
# Build the android app
|
||||
self.runner.current_platform = :android
|
||||
build
|
||||
if file.nil? || file.empty?
|
||||
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
|
||||
filename = app_name.gsub(" ", "_")
|
||||
|
||||
# Build the ios app
|
||||
self.runner.current_platform = :ios
|
||||
build
|
||||
if os_type == 'Android'
|
||||
file = "#{filename}.apk"
|
||||
elsif os_type == 'iOS'
|
||||
file = "#{filename}.ipa"
|
||||
file_plist = "#{filename}.plist"
|
||||
end
|
||||
end
|
||||
|
||||
build_folder_path = Dir[File.expand_path('..')].first
|
||||
file_path = "#{build_folder_path}/#{file}"
|
||||
|
||||
unless ENV['AWS_BUCKET_NAME'].nil? || ENV['AWS_BUCKET_NAME'].empty? || ENV['AWS_REGION'].nil? || ENV['AWS_REGION'].empty? || file_path.nil?
|
||||
s3_region = ENV['AWS_REGION']
|
||||
s3_bucket = ENV['AWS_BUCKET_NAME']
|
||||
s3_folder = ''
|
||||
|
||||
version_number = ''
|
||||
build_number = ''
|
||||
|
||||
if is_build_pr
|
||||
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{ENV['BRANCH_TO_BUILD']}"
|
||||
else
|
||||
if os_type == 'Android'
|
||||
version_number = android_get_version_name(gradle_file: './android/app/build.gradle')
|
||||
build_number = android_get_version_code(gradle_file: './android/app/build.gradle')
|
||||
elsif os_type == 'iOS'
|
||||
version_number = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost')
|
||||
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
|
||||
end
|
||||
|
||||
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
|
||||
end
|
||||
|
||||
s3 = Aws::S3::Resource.new(region: s3_region)
|
||||
file_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file}")
|
||||
file_obj.upload_file("#{file_path}")
|
||||
|
||||
if is_build_pr
|
||||
if os_type == 'Android'
|
||||
install_url = "https://#{s3_bucket}/#{s3_folder}/#{file}"
|
||||
elsif os_type == 'iOS'
|
||||
current_build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
|
||||
plist_template = File.read('plist.erb')
|
||||
plist_body = ERB.new(plist_template).result(binding)
|
||||
|
||||
plist_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file_plist}")
|
||||
plist_obj.put(body: plist_body)
|
||||
|
||||
install_url = "itms-services://?action=download-manifest&url=https://#{s3_bucket}/#{s3_folder}/#{file_plist}"
|
||||
end
|
||||
|
||||
qa_build_message({
|
||||
:os_type => os_type,
|
||||
:install_url => install_url
|
||||
})
|
||||
end
|
||||
|
||||
public_link = "https://#{s3_bucket}/#{s3_folder}/#{file}"
|
||||
if file == 'Mattermost-simulator-x86_64.app.zip'
|
||||
pretext = '#### New iOS build for VM/Simulator'
|
||||
msg = "Download link: #{public_link}"
|
||||
send_message_to_mattermost({
|
||||
:version_number => version_number,
|
||||
:build_number => build_number,
|
||||
:pretext => pretext,
|
||||
:title => '',
|
||||
:thumb_url => 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png',
|
||||
:msg => msg,
|
||||
:default_payloads => [],
|
||||
:success => true,
|
||||
})
|
||||
end
|
||||
|
||||
UI.success("S3 bucket @#{s3_bucket}, object @#{s3_folder}/#{file}")
|
||||
UI.success("S3 public path: #{public_link}")
|
||||
end
|
||||
end
|
||||
|
||||
platform :ios do
|
||||
before_all do
|
||||
if ENV['CIRCLECI'] == 'true'
|
||||
setup_circle_ci
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get iOS adhoc profiles'
|
||||
lane :adhoc do
|
||||
if ENV['MATCH_TYPE'] != 'adhoc' && !ENV['MATCH_PASSWORD'].nil? && !ENV['MATCH_PASSWORD'].empty?
|
||||
match(
|
||||
type: 'adhoc'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Build iOS app'
|
||||
lane :build do
|
||||
unless configured
|
||||
|
|
@ -321,7 +415,7 @@ platform :ios do
|
|||
)
|
||||
|
||||
# Sync the provisioning profiles using match
|
||||
if ENV['SYNC_PROVISIONING_PROFILES'] == 'true'
|
||||
if ENV['SYNC_PROVISIONING_PROFILES'] == 'true' && !ENV['MATCH_PASSWORD'].nil? && !ENV['MATCH_PASSWORD'].empty?
|
||||
match(type: ENV['MATCH_TYPE'] || 'adhoc')
|
||||
end
|
||||
end
|
||||
|
|
@ -333,6 +427,14 @@ platform :ios do
|
|||
end
|
||||
end
|
||||
|
||||
lane :deploy do |options|
|
||||
ipa_path = options[:ipa]
|
||||
|
||||
unless ipa_path.nil?
|
||||
submit_to_testflight(ipa_path)
|
||||
end
|
||||
end
|
||||
|
||||
error do |lane, exception|
|
||||
version = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost')
|
||||
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
|
||||
|
|
@ -348,12 +450,37 @@ platform :ios do
|
|||
})
|
||||
end
|
||||
|
||||
def setup_code_signing
|
||||
disable_automatic_code_signing(path: './ios/Mattermost.xcodeproj')
|
||||
|
||||
ENV['MATCH_APP_IDENTIFIER'].split(',').each do |id|
|
||||
target = 'Mattermost'
|
||||
if id.include? 'NotificationService'
|
||||
target = 'NotificationService'
|
||||
elsif id.include? 'MattermostShare'
|
||||
target = 'MattermostShare'
|
||||
end
|
||||
|
||||
profile = "sigh_#{id}_#{ENV['MATCH_TYPE']}"
|
||||
|
||||
update_project_provisioning(
|
||||
xcodeproj: './ios/Mattermost.xcodeproj',
|
||||
profile: ENV["#{profile}_profile-path"], # optional if you use sigh
|
||||
target_filter: ".*#{target}$", # matches name or type of a target
|
||||
build_configuration: 'Release',
|
||||
code_signing_identity: 'iPhone Distribution' # optionally specify the codesigning identity
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def build_ios()
|
||||
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
|
||||
app_name_sub = app_name.gsub(" ", "_")
|
||||
config_mode = ENV['BUILD_FOR_RELEASE'] == 'true' ? 'Release' : 'Debug'
|
||||
method = ENV['IOS_BUILD_EXPORT_METHOD'].nil? || ENV['IOS_BUILD_EXPORT_METHOD'].empty? ? 'ad-hoc' : ENV['IOS_BUILD_EXPORT_METHOD']
|
||||
|
||||
setup_code_signing
|
||||
|
||||
gym(
|
||||
clean: true,
|
||||
scheme: 'Mattermost',
|
||||
|
|
@ -362,6 +489,7 @@ platform :ios do
|
|||
export_method: method,
|
||||
skip_profile_detection: true,
|
||||
output_name: "#{app_name_sub}.ipa",
|
||||
export_xcargs: "-allowProvisioningUpdates",
|
||||
export_options: {
|
||||
signingStyle: 'manual',
|
||||
iCloudContainerEnvironment: 'Production'
|
||||
|
|
@ -483,6 +611,14 @@ platform :android do
|
|||
end
|
||||
end
|
||||
|
||||
lane :deploy do |options|
|
||||
apk_path = options[:apk]
|
||||
|
||||
unless apk_path.nil?
|
||||
submit_to_google_play(apk_path)
|
||||
end
|
||||
end
|
||||
|
||||
error do |lane, exception|
|
||||
build_number = android_get_version_code(
|
||||
gradle_file: './android/app/build.gradle'
|
||||
|
|
@ -587,83 +723,102 @@ def send_message_to_mattermost(options)
|
|||
end
|
||||
end
|
||||
|
||||
def submit_to_testflight(ipa_path)
|
||||
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
|
||||
app_name_sub = app_name.gsub(" ", "_")
|
||||
|
||||
if(File.file?(ipa_path))
|
||||
UI.success("ipa file #{ipa_path}")
|
||||
upload_to_tesflight(
|
||||
ipa: ipa_path
|
||||
)
|
||||
else
|
||||
UI.user_error! "ipa file does not exist #{ipa_path}"
|
||||
return
|
||||
end
|
||||
|
||||
version_number = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost')
|
||||
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
|
||||
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
|
||||
public_link = "https://#{ENV['AWS_BUCKET_NAME']}/#{s3_folder}/#{app_name_sub}.ipa"
|
||||
|
||||
|
||||
# Send a build message to Mattermost
|
||||
pretext = '#### New iOS released ready to be published'
|
||||
msg = "Release has been cut and is on TestFlight ready to be published.\nDownload link: #{public_link}"
|
||||
|
||||
if ENV['BETA_BUILD'] == 'true'
|
||||
pretext = '#### New iOS beta published to TestFlight'
|
||||
msg = "Sign up as a beta tester [here](https://testflight.apple.com/join/Q7Rx7K9P).\nDownload link: #{public_link}"
|
||||
end
|
||||
|
||||
send_message_to_mattermost({
|
||||
:version_number => version_number,
|
||||
:build_number => build_number,
|
||||
:pretext => pretext,
|
||||
:title => '',
|
||||
:thumb_url => 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png',
|
||||
:msg => msg,
|
||||
:default_payloads => [],
|
||||
:success => true,
|
||||
})
|
||||
end
|
||||
|
||||
def submit_to_google_play(apk_path)
|
||||
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
|
||||
app_name_sub = app_name.gsub(" ", "_")
|
||||
|
||||
if(File.file?(apk_path))
|
||||
UI.success("apk file #{apk_path}")
|
||||
unless ENV['SUPPLY_JSON_KEY'].nil? || ENV['SUPPLY_JSON_KEY'].empty?
|
||||
supply(
|
||||
track: ENV['SUPPLY_TRACK'] || 'alpha',
|
||||
apk: apk_path
|
||||
)
|
||||
end
|
||||
else
|
||||
UI.user_error! "apk file does not exist #{apk_path}"
|
||||
return
|
||||
end
|
||||
|
||||
version_number = android_get_version_name(gradle_file: './android/app/build.gradle')
|
||||
build_number = android_get_version_code(gradle_file: './android/app/build.gradle')
|
||||
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
|
||||
public_link = "https://#{ENV['AWS_BUCKET_NAME']}/#{s3_folder}/#{app_name_sub}.apk"
|
||||
|
||||
# Send a build message to Mattermost
|
||||
pretext = '#### New Android released ready to be published'
|
||||
msg = "Release has been cut and is on the Beta track ready to be published.\nDownload link: #{public_link}"
|
||||
|
||||
if ENV['BETA_BUILD'] == 'true'
|
||||
pretext = '#### New Android beta published to Google Play'
|
||||
msg = "Sign up as a beta tester [here](https://play.google.com/apps/testing/com.mattermost.rnbeta).\nDownload link: #{public_link}"
|
||||
end
|
||||
|
||||
send_message_to_mattermost({
|
||||
:version_number => version_number,
|
||||
:build_number => build_number,
|
||||
:pretext => pretext,
|
||||
:title => '',
|
||||
:thumb_url => 'https://lh3.ggpht.com/XL0CrI8skkxnboGct-duyg-bZ_MxJDTrjczyjdU8OP2PM1dmj7SP4jL1K8JQeMIB3AM=w300',
|
||||
:msg => msg,
|
||||
:default_payloads => [],
|
||||
:success => true,
|
||||
})
|
||||
end
|
||||
|
||||
def submit_to_store
|
||||
apk_path = lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]
|
||||
ipa_path = lane_context[SharedValues::IPA_OUTPUT_PATH]
|
||||
|
||||
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
|
||||
app_name_sub = app_name.gsub(" ", "_")
|
||||
|
||||
s3_region = ENV['AWS_REGION']
|
||||
s3_bucket = ENV['AWS_BUCKET_NAME']
|
||||
|
||||
# Submit to Google Play if required
|
||||
if !apk_path.nil? && ENV['SUBMIT_ANDROID_TO_GOOGLE_PLAY'] == 'true'
|
||||
UI.success("apk file #{apk_path}")
|
||||
|
||||
supply(
|
||||
track: ENV['SUPPLY_TRACK'] || 'alpha',
|
||||
apk: apk_path
|
||||
)
|
||||
|
||||
version_number = android_get_version_name(gradle_file: './android/app/build.gradle')
|
||||
build_number = android_get_version_code(gradle_file: './android/app/build.gradle')
|
||||
|
||||
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
|
||||
public_link = "https://#{s3_bucket}/#{s3_folder}/#{app_name_sub}.apk"
|
||||
|
||||
# Send a build message to Mattermost
|
||||
pretext = '#### New Android released ready to be published'
|
||||
msg = "Release has been cut and is on the Beta track ready to be published.\nDownload link: #{public_link}"
|
||||
|
||||
if ENV['BETA_BUILD'] == 'true'
|
||||
pretext = '#### New Android beta published to Google Play'
|
||||
msg = "Sign up as a beta tester [here](https://play.google.com/apps/testing/com.mattermost.rnbeta).\nDownload link: #{public_link}"
|
||||
end
|
||||
|
||||
send_message_to_mattermost({
|
||||
:version_number => version_number,
|
||||
:build_number => build_number,
|
||||
:pretext => pretext,
|
||||
:title => '',
|
||||
:thumb_url => 'https://lh3.ggpht.com/XL0CrI8skkxnboGct-duyg-bZ_MxJDTrjczyjdU8OP2PM1dmj7SP4jL1K8JQeMIB3AM=w300',
|
||||
:msg => msg,
|
||||
:default_payloads => [],
|
||||
:success => true,
|
||||
})
|
||||
submit_to_google_play(apk_path)
|
||||
end
|
||||
|
||||
# Submit to TestFlight if required
|
||||
if !ipa_path.nil? && ENV['SUBMIT_IOS_TO_TESTFLIGHT'] == 'true'
|
||||
UI.success("ipa file #{ipa_path}")
|
||||
|
||||
pilot
|
||||
|
||||
version_number = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost')
|
||||
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
|
||||
|
||||
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
|
||||
public_link = "https://#{s3_bucket}/#{s3_folder}/#{app_name_sub}.ipa"
|
||||
|
||||
# Send a build message to Mattermost
|
||||
pretext = '#### New iOS released ready to be published'
|
||||
msg = "Release has been cut and is on TestFlight ready to be published.\nDownload link: #{public_link}"
|
||||
|
||||
if ENV['BETA_BUILD'] == 'true'
|
||||
pretext = '#### New iOS beta published to TestFlight'
|
||||
msg = "Sign up as a beta tester [here](https://testflight.apple.com/join/Q7Rx7K9P).\nDownload link: #{public_link}"
|
||||
end
|
||||
|
||||
send_message_to_mattermost({
|
||||
:version_number => version_number,
|
||||
:build_number => build_number,
|
||||
:pretext => pretext,
|
||||
:title => '',
|
||||
:thumb_url => 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png',
|
||||
:msg => msg,
|
||||
:default_payloads => [],
|
||||
:success => true,
|
||||
})
|
||||
submit_to_testflight(ipa_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -686,92 +841,3 @@ def qa_build_message(options)
|
|||
UI.success("PR Built for #{os_type}: #{install_url}")
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Upload file to s3'
|
||||
lane :upload_file_to_s3 do |options|
|
||||
os_type = options[:os_type]
|
||||
file = options[:file]
|
||||
file_plist = ""
|
||||
|
||||
if file.nil? || file.empty?
|
||||
app_name = ENV['APP_NAME'] || 'Mattermost Beta'
|
||||
filename = app_name.gsub(" ", "_")
|
||||
|
||||
if os_type == 'Android'
|
||||
file = "#{filename}.apk"
|
||||
elsif os_type == 'iOS'
|
||||
file = "#{filename}.ipa"
|
||||
file_plist = "#{filename}.plist"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
build_folder_path = Dir[File.expand_path('..')].first
|
||||
file_path = "#{build_folder_path}/#{file}"
|
||||
|
||||
unless ENV['AWS_BUCKET_NAME'].nil? || ENV['AWS_BUCKET_NAME'].empty? || ENV['AWS_REGION'].nil? || ENV['AWS_REGION'].empty? || file_path.nil?
|
||||
s3_region = ENV['AWS_REGION']
|
||||
s3_bucket = ENV['AWS_BUCKET_NAME']
|
||||
s3_folder = ''
|
||||
|
||||
version_number = ''
|
||||
build_number = ''
|
||||
|
||||
if is_build_pr
|
||||
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{ENV['BRANCH_TO_BUILD']}"
|
||||
else
|
||||
if os_type == 'Android'
|
||||
version_number = android_get_version_name(gradle_file: './android/app/build.gradle')
|
||||
build_number = android_get_version_code(gradle_file: './android/app/build.gradle')
|
||||
elsif os_type == 'iOS'
|
||||
version_number = get_version_number(xcodeproj: './ios/Mattermost.xcodeproj', target: 'Mattermost')
|
||||
build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
|
||||
end
|
||||
|
||||
s3_folder = "#{ENV['AWS_FOLDER_NAME']}/#{version_number}/#{build_number}"
|
||||
end
|
||||
|
||||
s3 = Aws::S3::Resource.new(region: s3_region)
|
||||
file_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file}")
|
||||
file_obj.upload_file("#{file_path}")
|
||||
|
||||
if is_build_pr
|
||||
if os_type == 'Android'
|
||||
install_url = "https://#{s3_bucket}/#{s3_folder}/#{file}"
|
||||
elsif os_type == 'iOS'
|
||||
current_build_number = get_build_number(xcodeproj: './ios/Mattermost.xcodeproj')
|
||||
plist_template = File.read('plist.erb')
|
||||
plist_body = ERB.new(plist_template).result(binding)
|
||||
|
||||
plist_obj = s3.bucket(s3_bucket).object("#{s3_folder}/#{file_plist}")
|
||||
plist_obj.put(body: plist_body)
|
||||
|
||||
install_url = "itms-services://?action=download-manifest&url=https://#{s3_bucket}/#{s3_folder}/#{file_plist}"
|
||||
end
|
||||
|
||||
qa_build_message({
|
||||
:os_type => os_type,
|
||||
:install_url => install_url
|
||||
})
|
||||
end
|
||||
|
||||
public_link = "https://#{s3_bucket}/#{s3_folder}/#{file}"
|
||||
if file == 'Mattermost-simulator-x86_64.app.zip'
|
||||
pretext = '#### New iOS build for VM/Simulator'
|
||||
msg = "Download link: #{public_link}"
|
||||
send_message_to_mattermost({
|
||||
:version_number => version_number,
|
||||
:build_number => build_number,
|
||||
:pretext => pretext,
|
||||
:title => '',
|
||||
:thumb_url => 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png',
|
||||
:msg => msg,
|
||||
:default_payloads => [],
|
||||
:success => true,
|
||||
})
|
||||
end
|
||||
|
||||
UI.success("S3 bucket @#{s3_bucket}, object @#{s3_folder}/#{file}")
|
||||
UI.success("S3 public path: #{public_link}")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2637,21 +2637,17 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh",
|
||||
"${PODS_ROOT}/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.bundle",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/WKYTPlayerView.bundle",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh\"\n";
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Mattermost/Pods-Mattermost-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
AE4769B235D14E6C9C64EA78 /* Upload Debug Symbols to Sentry */ = {
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@
|
|||
|
||||
@implementation AppDelegate
|
||||
|
||||
NSString* const NotificationMessageAction = @"message";
|
||||
NSString* const NotificationClearAction = @"clear";
|
||||
NSString* const NotificationUpdateBadgeAction = @"update_badge";
|
||||
NSString* const NOTIFICATION_MESSAGE_ACTION = @"message";
|
||||
NSString* const NOTIFICATION_CLEAR_ACTION = @"clear";
|
||||
NSString* const NOTIFICATION_UPDATE_BADGE_ACTION = @"update_badge";
|
||||
|
||||
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler {
|
||||
os_log(OS_LOG_DEFAULT, "Mattermost will attach session from handleEventsForBackgroundURLSession!! identifier=%{public}@", identifier);
|
||||
|
|
@ -60,18 +60,14 @@ NSString* const NotificationUpdateBadgeAction = @"update_badge";
|
|||
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error: nil];
|
||||
|
||||
[RNNotifications startMonitorNotifications];
|
||||
|
||||
os_log(OS_LOG_DEFAULT, "Mattermost started!!");
|
||||
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Required to register for notifications
|
||||
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
|
||||
{
|
||||
[RNNotifications didRegisterUserNotificationSettings:notificationSettings];
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
|
||||
{
|
||||
[RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
|
||||
|
|
@ -81,7 +77,34 @@ NSString* const NotificationUpdateBadgeAction = @"update_badge";
|
|||
[RNNotifications didFailToRegisterForRemoteNotificationsWithError:error];
|
||||
}
|
||||
|
||||
-(void)cleanNotificationsFromChannel:(NSString *)channelId andUpdateBadge:(BOOL)updateBadge {
|
||||
// Required for the notification event.
|
||||
|
||||
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler {
|
||||
UIApplicationState state = [UIApplication sharedApplication].applicationState;
|
||||
NSString* action = [userInfo objectForKey:@"type"];
|
||||
NSString* channelId = [userInfo objectForKey:@"channel_id"];
|
||||
NSString* ackId = [userInfo objectForKey:@"ack_id"];
|
||||
|
||||
if (action && [action isEqualToString: NOTIFICATION_CLEAR_ACTION]) {
|
||||
// If received a notification that a channel was read, remove all notifications from that channel (only with app in foreground/background)
|
||||
[self cleanNotificationsFromChannel:channelId];
|
||||
RuntimeUtils *utils = [[RuntimeUtils alloc] init];
|
||||
[[UploadSession shared] notificationReceiptWithNotificationId:ackId receivedAt:round([[NSDate date] timeIntervalSince1970] * 1000.0) type:action];
|
||||
[utils delayWithSeconds:0.2 closure:^(void) {
|
||||
// This is to notify the NotificationCenter that something has changed.
|
||||
completionHandler(UIBackgroundFetchResultNewData);
|
||||
}];
|
||||
|
||||
return;
|
||||
} else if (state == UIApplicationStateInactive) {
|
||||
// When the notification is opened
|
||||
[self cleanNotificationsFromChannel:channelId];
|
||||
}
|
||||
|
||||
completionHandler(UIBackgroundFetchResultNoData);
|
||||
}
|
||||
|
||||
-(void)cleanNotificationsFromChannel:(NSString *)channelId {
|
||||
if ([UNUserNotificationCenter class]) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
[center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * _Nonnull notifications) {
|
||||
|
|
@ -99,64 +122,10 @@ NSString* const NotificationUpdateBadgeAction = @"update_badge";
|
|||
}
|
||||
|
||||
[center removeDeliveredNotificationsWithIdentifiers:notificationIds];
|
||||
NSInteger removed = (NSInteger)[notificationIds count] + 1;
|
||||
if (removed > 0 && updateBadge) {
|
||||
NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber;
|
||||
NSInteger count = badge - removed;
|
||||
if (count > 0) {
|
||||
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:count];
|
||||
} else {
|
||||
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
// Required for the notification event.
|
||||
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler {
|
||||
UIApplicationState state = [UIApplication sharedApplication].applicationState;
|
||||
NSString* action = [userInfo objectForKey:@"type"];
|
||||
NSString* channelId = [userInfo objectForKey:@"channel_id"];
|
||||
NSString* ackId = [userInfo objectForKey:@"ack_id"];
|
||||
|
||||
if (action && [action isEqualToString: NotificationClearAction]) {
|
||||
// If received a notification that a channel was read, remove all notifications from that channel (only with app in foreground/background)
|
||||
[self cleanNotificationsFromChannel:channelId andUpdateBadge:NO];
|
||||
RuntimeUtils *utils = [[RuntimeUtils alloc] init];
|
||||
[[UploadSession shared] notificationReceiptWithNotificationId:ackId receivedAt:round([[NSDate date] timeIntervalSince1970] * 1000.0) type:action];
|
||||
[utils delayWithSeconds:0.2 closure:^(void) {
|
||||
// This is to notify the NotificationCenter that something has changed.
|
||||
completionHandler(UIBackgroundFetchResultNewData);
|
||||
}];
|
||||
|
||||
return;
|
||||
} else if (state == UIApplicationStateInactive) {
|
||||
// When the notification is opened
|
||||
[self cleanNotificationsFromChannel:channelId andUpdateBadge:NO];
|
||||
}
|
||||
|
||||
[RNNotifications didReceiveRemoteNotification:userInfo];
|
||||
completionHandler(UIBackgroundFetchResultNoData);
|
||||
}
|
||||
|
||||
// Required for the localNotification event.
|
||||
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
|
||||
{
|
||||
[RNNotifications didReceiveLocalNotification:notification];
|
||||
}
|
||||
|
||||
// Required for the notification actions.
|
||||
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler
|
||||
{
|
||||
[RNNotifications handleActionWithIdentifier:identifier forLocalNotification:notification withResponseInfo:responseInfo completionHandler:completionHandler];
|
||||
}
|
||||
|
||||
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler
|
||||
{
|
||||
[RNNotifications handleActionWithIdentifier:identifier forRemoteNotification:userInfo withResponseInfo:responseInfo completionHandler:completionHandler];
|
||||
}
|
||||
|
||||
// Required for deeplinking
|
||||
|
||||
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ SPEC CHECKSUMS:
|
|||
|
||||
PODFILE CHECKSUM: e565d3af8eabb0e489b73c79433e140e30f8fa9c
|
||||
|
||||
COCOAPODS: 1.5.3
|
||||
COCOAPODS: 1.7.5
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ const config = {
|
|||
};
|
||||
},
|
||||
},
|
||||
maxWorkers: 4,
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
|
|
|||
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -16739,8 +16739,9 @@
|
|||
}
|
||||
},
|
||||
"react-native-notifications": {
|
||||
"version": "github:mattermost/react-native-notifications#1b7ec8513606b42237ab4674de9dacb4d1935e38",
|
||||
"from": "github:mattermost/react-native-notifications#1b7ec8513606b42237ab4674de9dacb4d1935e38",
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/react-native-notifications/-/react-native-notifications-2.0.6.tgz",
|
||||
"integrity": "sha512-NFx5ADlqfQYTFkKWvd/GxM8rxKf1lSWJZJY0jbydAOZAuhnKFR/CsH7Mpx6T+9pY5Z3rvu7UzBtVn9LTBx0jYg==",
|
||||
"requires": {
|
||||
"core-js": "^1.0.0",
|
||||
"uuid": "^2.0.3"
|
||||
|
|
@ -17586,7 +17587,6 @@
|
|||
"version": "git+https://github.com/enahum/redux-offline.git#885024de96b6ec73650c340c8928066585c413df",
|
||||
"from": "git+https://github.com/enahum/redux-offline.git#885024de96b6ec73650c340c8928066585c413df",
|
||||
"requires": {
|
||||
"@react-native-community/netinfo": "^4.1.3",
|
||||
"redux-persist": "^4.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
"react-native-linear-gradient": "2.5.4",
|
||||
"react-native-local-auth": "github:mattermost/react-native-local-auth#cc9ce2f468fbf7b431dfad3191a31aaa9227a6ab",
|
||||
"react-native-navigation": "2.21.1",
|
||||
"react-native-notifications": "github:mattermost/react-native-notifications#1b7ec8513606b42237ab4674de9dacb4d1935e38",
|
||||
"react-native-notifications": "2.0.6",
|
||||
"react-native-passcode-status": "1.1.1",
|
||||
"react-native-permissions": "1.1.1",
|
||||
"react-native-safe-area": "0.5.1",
|
||||
|
|
|
|||
378
patches/react-native-notifications+2.0.6.patch
Normal file
378
patches/react-native-notifications+2.0.6.patch
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
diff --git a/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml b/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml
|
||||
index ffef75f..a4df210 100644
|
||||
--- a/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml
|
||||
+++ b/node_modules/react-native-notifications/android/app/src/main/AndroidManifest.xml
|
||||
@@ -32,6 +32,8 @@
|
||||
<service
|
||||
android:name=".gcm.FcmInstanceIdRefreshHandlerService"
|
||||
android:exported="false" />
|
||||
+
|
||||
+ <receiver android:name="com.wix.reactnativenotifications.core.notification.PushNotificationPublisher" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
|
||||
index 8fb5f01..74d6138 100644
|
||||
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
|
||||
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/RNNotificationsModule.java
|
||||
@@ -103,12 +103,26 @@ public class RNNotificationsModule extends ReactContextBaseJavaModule implements
|
||||
pushNotification.onPostRequest(notificationId);
|
||||
}
|
||||
|
||||
+ @ReactMethod
|
||||
+ public void scheduleLocalNotification(ReadableMap notificationPropsMap, int notificationId) {
|
||||
+ Log.d(LOGTAG, "Native method invocation: scheduleLocalNotification");
|
||||
+ final Bundle notificationProps = Arguments.toBundle(notificationPropsMap);
|
||||
+ final IPushNotification pushNotification = PushNotification.get(getReactApplicationContext().getApplicationContext(), notificationProps);
|
||||
+ pushNotification.onScheduleRequest(notificationId);
|
||||
+ }
|
||||
+
|
||||
@ReactMethod
|
||||
public void cancelLocalNotification(int notificationId) {
|
||||
IPushNotificationsDrawer notificationsDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
|
||||
notificationsDrawer.onNotificationClearRequest(notificationId);
|
||||
}
|
||||
|
||||
+ @ReactMethod
|
||||
+ public void cancelAllLocalNotifications() {
|
||||
+ IPushNotificationsDrawer notificationDrawer = PushNotificationsDrawer.get(getReactApplicationContext().getApplicationContext());
|
||||
+ notificationDrawer.onCancelAllLocalNotifications();
|
||||
+ }
|
||||
+
|
||||
@ReactMethod
|
||||
public void isRegisteredForRemoteNotifications(Promise promise) {
|
||||
boolean hasPermission = NotificationManagerCompat.from(getReactApplicationContext()).areNotificationsEnabled();
|
||||
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/helpers/ScheduleNotificationHelper.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/helpers/ScheduleNotificationHelper.java
|
||||
new file mode 100644
|
||||
index 0000000..c35076d
|
||||
--- /dev/null
|
||||
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/helpers/ScheduleNotificationHelper.java
|
||||
@@ -0,0 +1,90 @@
|
||||
+package com.wix.reactnativenotifications.core.helpers;
|
||||
+
|
||||
+import android.app.AlarmManager;
|
||||
+import android.os.Build;
|
||||
+import android.os.Bundle;
|
||||
+import android.content.Context;
|
||||
+import android.content.Intent;
|
||||
+import android.app.PendingIntent;
|
||||
+import android.content.SharedPreferences;
|
||||
+import android.util.Log;
|
||||
+
|
||||
+import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
|
||||
+import com.wix.reactnativenotifications.core.notification.PushNotificationPublisher;
|
||||
+
|
||||
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
|
||||
+
|
||||
+public class ScheduleNotificationHelper {
|
||||
+ public static ScheduleNotificationHelper sInstance;
|
||||
+ public static final String PREFERENCES_KEY = "rn_push_notification";
|
||||
+ static final String NOTIFICATION_ID = "notificationId";
|
||||
+
|
||||
+ private final SharedPreferences scheduledNotificationsPersistence;
|
||||
+ protected final Context mContext;
|
||||
+
|
||||
+ private ScheduleNotificationHelper(Context context) {
|
||||
+ this.mContext = context;
|
||||
+ this.scheduledNotificationsPersistence = context.getSharedPreferences(ScheduleNotificationHelper.PREFERENCES_KEY, Context.MODE_PRIVATE);
|
||||
+ }
|
||||
+
|
||||
+ public static ScheduleNotificationHelper getInstance(Context context) {
|
||||
+ if (sInstance == null) {
|
||||
+ sInstance = new ScheduleNotificationHelper(context);
|
||||
+ }
|
||||
+ return sInstance;
|
||||
+ }
|
||||
+
|
||||
+ public PendingIntent createPendingNotificationIntent(Bundle bundle) {
|
||||
+ Integer notificationId = Integer.valueOf(bundle.getString("id"));
|
||||
+ Intent notificationIntent = new Intent(mContext, PushNotificationPublisher.class);
|
||||
+ notificationIntent.putExtra(ScheduleNotificationHelper.NOTIFICATION_ID, notificationId);
|
||||
+ notificationIntent.putExtras(bundle);
|
||||
+ return PendingIntent.getBroadcast(mContext, notificationId, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
+ }
|
||||
+
|
||||
+ public void schedulePendingNotificationIntent(PendingIntent intent, long fireDate) {
|
||||
+ AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
|
||||
+
|
||||
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, fireDate, intent);
|
||||
+ } else {
|
||||
+ alarmManager.set(AlarmManager.RTC_WAKEUP, fireDate, intent);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public void cancelScheduledNotificationIntent(PendingIntent intent) {
|
||||
+ AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
|
||||
+ alarmManager.cancel(intent);
|
||||
+ }
|
||||
+
|
||||
+ public boolean savePreferences(String notificationId, PushNotificationProps notificationProps) {
|
||||
+ SharedPreferences.Editor editor = scheduledNotificationsPersistence.edit();
|
||||
+ editor.putString(notificationId, notificationProps.toString());
|
||||
+ commit(editor);
|
||||
+
|
||||
+ return scheduledNotificationsPersistence.contains(notificationId);
|
||||
+ }
|
||||
+
|
||||
+ public void removePreference(String notificationId) {
|
||||
+ if (scheduledNotificationsPersistence.contains(notificationId)) {
|
||||
+ // remove it from local storage
|
||||
+ SharedPreferences.Editor editor = scheduledNotificationsPersistence.edit();
|
||||
+ editor.remove(notificationId);
|
||||
+ commit(editor);
|
||||
+ } else {
|
||||
+ Log.w(LOGTAG, "Unable to find notification " + notificationId);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public java.util.Set<String> getPreferencesKeys() {
|
||||
+ return scheduledNotificationsPersistence.getAll().keySet();
|
||||
+ }
|
||||
+
|
||||
+ private static void commit(SharedPreferences.Editor editor) {
|
||||
+ if (Build.VERSION.SDK_INT < 9) {
|
||||
+ editor.commit();
|
||||
+ } else {
|
||||
+ editor.apply();
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java
|
||||
index 0d70024..47b962e 100644
|
||||
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java
|
||||
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/IPushNotification.java
|
||||
@@ -26,5 +26,20 @@ public interface IPushNotification {
|
||||
*/
|
||||
int onPostRequest(Integer notificationId);
|
||||
|
||||
+ /**
|
||||
+ * Handle a request to schedule this notification.
|
||||
+ *
|
||||
+ * @param notificationId The specific ID to associated with the notification.
|
||||
+ */
|
||||
+ void onScheduleRequest(Integer notificationId);
|
||||
+
|
||||
+ /**
|
||||
+ * Handle a request to post this scheduled notification.
|
||||
+ *
|
||||
+ * @param notificationId The specific ID to associated with the notification.
|
||||
+ * @return The ID assigned to the notification.
|
||||
+ */
|
||||
+ int onPostScheduledRequest(Integer notificationId);
|
||||
+
|
||||
PushNotificationProps asProps();
|
||||
}
|
||||
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
|
||||
index 5e4e3d2..871e157 100644
|
||||
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
|
||||
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.wix.reactnativenotifications.core.notification;
|
||||
|
||||
+import android.app.AlarmManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
@@ -20,7 +21,9 @@ import com.wix.reactnativenotifications.core.InitialNotificationHolder;
|
||||
import com.wix.reactnativenotifications.core.JsIOHelper;
|
||||
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
||||
import com.wix.reactnativenotifications.core.ProxyService;
|
||||
+import com.wix.reactnativenotifications.core.helpers.ScheduleNotificationHelper;
|
||||
|
||||
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
|
||||
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_OPENED_EVENT_NAME;
|
||||
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
|
||||
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_FOREGROUND_EVENT_NAME;
|
||||
@@ -80,6 +83,41 @@ public class PushNotification implements IPushNotification {
|
||||
return postNotification(notificationId);
|
||||
}
|
||||
|
||||
+ @Override
|
||||
+ public void onScheduleRequest(Integer notificationId) {
|
||||
+ Bundle bundle = mNotificationProps.asBundle();
|
||||
+
|
||||
+ if (bundle.getString("message") == null) {
|
||||
+ Log.e(LOGTAG, "No message specified for the scheduled notification");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ double date = bundle.getDouble("fireDate");
|
||||
+ if (date == 0) {
|
||||
+ Log.e(LOGTAG, "No date specified for the scheduled notification");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ ScheduleNotificationHelper helper = ScheduleNotificationHelper.getInstance(mContext);
|
||||
+ String notificationIdStr = Integer.toString(notificationId);
|
||||
+ boolean isSaved = helper.savePreferences(notificationIdStr, mNotificationProps);
|
||||
+ if (!isSaved) {
|
||||
+ Log.e(LOGTAG, "Failed to save preference for notificationId " + notificationIdStr);
|
||||
+ }
|
||||
+
|
||||
+ PendingIntent pendingIntent = helper.createPendingNotificationIntent(bundle);
|
||||
+ long fireDate = (long) date;
|
||||
+ helper.schedulePendingNotificationIntent(pendingIntent, fireDate);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int onPostScheduledRequest(Integer notificationId) {
|
||||
+ ScheduleNotificationHelper helper = ScheduleNotificationHelper.getInstance(mContext);
|
||||
+ helper.removePreference(String.valueOf(notificationId));
|
||||
+
|
||||
+ return postNotification(notificationId);
|
||||
+ }
|
||||
+
|
||||
@Override
|
||||
public PushNotificationProps asProps() {
|
||||
return mNotificationProps.copy();
|
||||
@@ -140,11 +178,12 @@ public class PushNotification implements IPushNotification {
|
||||
}
|
||||
|
||||
protected Notification buildNotification(PendingIntent intent) {
|
||||
- return getNotificationBuilder(intent).build();
|
||||
+ Notification.Builder builder = getNotificationBuilder(intent);
|
||||
+ Notification notification = builder.build();
|
||||
+ return notification;
|
||||
}
|
||||
|
||||
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
|
||||
-
|
||||
String CHANNEL_ID = "channel_01";
|
||||
String CHANNEL_NAME = "Channel Name";
|
||||
|
||||
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotificationPublisher.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotificationPublisher.java
|
||||
new file mode 100644
|
||||
index 0000000..5b64593
|
||||
--- /dev/null
|
||||
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotificationPublisher.java
|
||||
@@ -0,0 +1,27 @@
|
||||
+package com.wix.reactnativenotifications.core.notification;
|
||||
+
|
||||
+import android.app.Application;
|
||||
+import android.content.BroadcastReceiver;
|
||||
+import android.content.Context;
|
||||
+import android.content.Intent;
|
||||
+import android.util.Log;
|
||||
+
|
||||
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
|
||||
+
|
||||
+public class PushNotificationPublisher extends BroadcastReceiver {
|
||||
+ final static String NOTIFICATION_ID = "notificationId";
|
||||
+
|
||||
+ @Override
|
||||
+ public void onReceive(Context context, Intent intent) {
|
||||
+ Log.d(LOGTAG, "Received scheduled notification intent");
|
||||
+ int notificationId = intent.getIntExtra(NOTIFICATION_ID, 0);
|
||||
+ long currentTime = System.currentTimeMillis();
|
||||
+
|
||||
+ Application applicationContext = (Application) context.getApplicationContext();
|
||||
+ final IPushNotification pushNotification = PushNotification.get(applicationContext, intent.getExtras());
|
||||
+
|
||||
+ Log.i(LOGTAG, "PushNotificationPublisher: Prepare To Publish: " + notificationId + ", Now Time: " + currentTime);
|
||||
+
|
||||
+ pushNotification.onPostScheduledRequest(notificationId);
|
||||
+ }
|
||||
+}
|
||||
\ No newline at end of file
|
||||
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java
|
||||
index 3be3dc1..7027958 100644
|
||||
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java
|
||||
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/IPushNotificationsDrawer.java
|
||||
@@ -9,4 +9,5 @@ public interface IPushNotificationsDrawer {
|
||||
|
||||
void onNotificationOpened();
|
||||
void onNotificationClearRequest(int id);
|
||||
+ void onCancelAllLocalNotifications();
|
||||
}
|
||||
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java
|
||||
index 7b320e1..d95535b 100644
|
||||
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java
|
||||
+++ b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notificationdrawer/PushNotificationsDrawer.java
|
||||
@@ -2,10 +2,16 @@ package com.wix.reactnativenotifications.core.notificationdrawer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
+import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
+import android.os.Bundle;
|
||||
+import android.util.Log;
|
||||
|
||||
import com.wix.reactnativenotifications.core.AppLaunchHelper;
|
||||
import com.wix.reactnativenotifications.core.InitialNotificationHolder;
|
||||
+import com.wix.reactnativenotifications.core.helpers.ScheduleNotificationHelper;
|
||||
+
|
||||
+import static com.wix.reactnativenotifications.Defs.LOGTAG;
|
||||
|
||||
public class PushNotificationsDrawer implements IPushNotificationsDrawer {
|
||||
|
||||
@@ -60,8 +66,41 @@ public class PushNotificationsDrawer implements IPushNotificationsDrawer {
|
||||
notificationManager.cancel(id);
|
||||
}
|
||||
|
||||
+ @Override
|
||||
+ public void onCancelAllLocalNotifications() {
|
||||
+ clearAll();
|
||||
+ cancelAllScheduledNotifications();
|
||||
+ }
|
||||
+
|
||||
protected void clearAll() {
|
||||
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancelAll();
|
||||
}
|
||||
+
|
||||
+ protected void cancelAllScheduledNotifications() {
|
||||
+ Log.i(LOGTAG, "Cancelling all scheduled notifications");
|
||||
+ ScheduleNotificationHelper helper = ScheduleNotificationHelper.getInstance(mContext);
|
||||
+
|
||||
+ for (String notificationId : helper.getPreferencesKeys()) {
|
||||
+ cancelScheduledNotification(notificationId);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ protected void cancelScheduledNotification(String notificationId) {
|
||||
+ Log.i(LOGTAG, "Cancelling scheduled notification: " + notificationId);
|
||||
+
|
||||
+ ScheduleNotificationHelper helper = ScheduleNotificationHelper.getInstance(mContext);
|
||||
+
|
||||
+ // Remove it from the alarm manger schedule
|
||||
+ Bundle bundle = new Bundle();
|
||||
+ bundle.putString("id", notificationId);
|
||||
+ PendingIntent pendingIntent = helper.createPendingNotificationIntent(bundle);
|
||||
+ helper.cancelScheduledNotificationIntent(pendingIntent);
|
||||
+
|
||||
+ helper.removePreference(notificationId);
|
||||
+
|
||||
+ // Remove it from the notification center
|
||||
+ final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
+ notificationManager.cancel(Integer.parseInt(notificationId));
|
||||
+ }
|
||||
}
|
||||
diff --git a/node_modules/react-native-notifications/lib/src/index.android.js b/node_modules/react-native-notifications/lib/src/index.android.js
|
||||
index 51376bf..a5d9540 100644
|
||||
--- a/node_modules/react-native-notifications/lib/src/index.android.js
|
||||
+++ b/node_modules/react-native-notifications/lib/src/index.android.js
|
||||
@@ -67,9 +67,22 @@ export class NotificationsAndroid {
|
||||
return id;
|
||||
}
|
||||
|
||||
+ static scheduleLocalNotification(notification: Object) {
|
||||
+ const id = Math.random() * 100000000 | 0; // Bitwise-OR forces value onto a 32bit limit
|
||||
+ if (!notification.hasOwnProperty('id')) {
|
||||
+ notification.id = id.toString();
|
||||
+ }
|
||||
+ RNNotifications.scheduleLocalNotification(notification, id);
|
||||
+ return id;
|
||||
+ }
|
||||
+
|
||||
static cancelLocalNotification(id) {
|
||||
RNNotifications.cancelLocalNotification(id);
|
||||
}
|
||||
+
|
||||
+ static cancelAllLocalNotifications() {
|
||||
+ RNNotifications.cancelAllLocalNotifications();
|
||||
+ }
|
||||
}
|
||||
|
||||
export class PendingNotifications {
|
||||
Loading…
Reference in a new issue