Merge branch 'master' into mark-as-unread
This commit is contained in:
commit
bf68a33bab
180 changed files with 9307 additions and 15863 deletions
|
|
@ -159,39 +159,30 @@ commands:
|
|||
no_output_timeout: 30m
|
||||
command: bundle exec fastlane ios build || exit 1
|
||||
|
||||
deploy-android:
|
||||
description: "Deploy apk to Google Play"
|
||||
deploy-to-store:
|
||||
description: "Deploy build to store"
|
||||
parameters:
|
||||
apk_path:
|
||||
task:
|
||||
type: string
|
||||
target:
|
||||
type: string
|
||||
file:
|
||||
type: string
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/mattermost-mobile
|
||||
- deploy:
|
||||
name: "Deploy apk to Google Play"
|
||||
at: /
|
||||
- run:
|
||||
name: <<parameters.task>>
|
||||
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>>
|
||||
command: bundle exec fastlane <<parameters.target>> deploy file:$HOME/mattermost-mobile/<<parameters.file>>
|
||||
|
||||
persist:
|
||||
description: "Persist mattermost-mobile directory"
|
||||
steps:
|
||||
- persist_to_workspace:
|
||||
root: ./
|
||||
root: /
|
||||
paths:
|
||||
- ./
|
||||
- mattermost-mobile*
|
||||
|
||||
save:
|
||||
description: "Save binaries artifacts"
|
||||
|
|
@ -244,7 +235,6 @@ jobs:
|
|||
BRANCH_TO_BUILD: ${CIRCLE_BRANCH}
|
||||
steps:
|
||||
- build-android
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost_Beta.apk"
|
||||
|
||||
|
|
@ -270,7 +260,6 @@ jobs:
|
|||
BRANCH_TO_BUILD: ${CIRCLE_BRANCH}
|
||||
steps:
|
||||
- build-ios
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost_Beta.ipa"
|
||||
|
||||
|
|
@ -279,28 +268,36 @@ jobs:
|
|||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- deploy-android:
|
||||
apk_path: Mattermost.apk
|
||||
- deploy-to-store:
|
||||
task: "Deploy to Google Play"
|
||||
target: android
|
||||
file: Mattermost.apk
|
||||
|
||||
deploy-android-beta:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- deploy-android:
|
||||
apk_path: Mattermost_Beta.apk
|
||||
- deploy-to-store:
|
||||
task: "Deploy to Google Play"
|
||||
target: android
|
||||
file: Mattermost_Beta.apk
|
||||
|
||||
deploy-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
- deploy-ios:
|
||||
ipa_path: Mattermost.ipa
|
||||
- deploy-to-store:
|
||||
task: "Deploy to TestFlight"
|
||||
target: ios
|
||||
file: Mattermost.ipa
|
||||
|
||||
deploy-ios-beta:
|
||||
executor: ios
|
||||
steps:
|
||||
- deploy-ios:
|
||||
ipa_path: Mattermost_Beta.ipa
|
||||
- deploy-to-store:
|
||||
task: "Deploy to TestFlight"
|
||||
target: ios
|
||||
file: Mattermost_Beta.ipa
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
|
|
|||
77
.flowconfig
77
.flowconfig
|
|
@ -5,26 +5,24 @@
|
|||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; Ignore unexpected extra "@providesModule"
|
||||
.*/node_modules/.*/node_modules/fbjs/.*
|
||||
|
||||
; Ignore duplicate module providers
|
||||
; For RN Apps installed via npm, "Libraries" folder is inside
|
||||
; "node_modules/react-native" but in the source repo it is in the root
|
||||
.*/Libraries/react-native/React.js
|
||||
|
||||
; Ignore polyfills
|
||||
.*/Libraries/polyfills/.*
|
||||
node_modules/react-native/Libraries/polyfills/.*
|
||||
|
||||
; Ignore metro
|
||||
.*/node_modules/metro/.*
|
||||
; These should not be required directly
|
||||
; require from fbjs/lib instead: require('fbjs/lib/warning')
|
||||
node_modules/warning/.*
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/Libraries/Utilities/LoadingView.js
|
||||
|
||||
[untyped]
|
||||
.*/node_modules/@react-native-community/cli/.*/.*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
node_modules/react-native/Libraries/react-native/react-native-interface.js
|
||||
node_modules/react-native/flow/
|
||||
node_modules/react-native/flow-github/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
|
|
@ -32,39 +30,46 @@ emoji=true
|
|||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
module.system=haste
|
||||
module.system.haste.use_name_reducers=true
|
||||
# get basename
|
||||
module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
|
||||
# strip .js or .js.flow suffix
|
||||
module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
|
||||
# strip .ios suffix
|
||||
module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
|
||||
module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
|
||||
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
|
||||
module.system.haste.paths.blacklist=.*/__tests__/.*
|
||||
module.system.haste.paths.blacklist=.*/__mocks__/.*
|
||||
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
|
||||
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
|
||||
module.file_ext=.js
|
||||
module.file_ext=.json
|
||||
module.file_ext=.ios.js
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
|
||||
|
||||
module.file_ext=.js
|
||||
module.file_ext=.jsx
|
||||
module.file_ext=.json
|
||||
module.file_ext=.native.js
|
||||
module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/react-native/react-native-implementation'
|
||||
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/node_modules/react-native/\1'
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/Image/RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
[lints]
|
||||
sketchy-null-number=warn
|
||||
sketchy-null-mixed=warn
|
||||
sketchy-number=warn
|
||||
untyped-type-import=warn
|
||||
nonstrict-import=warn
|
||||
deprecated-type=warn
|
||||
unsafe-getters-setters=warn
|
||||
inexact-spread=warn
|
||||
unnecessary-invariant=warn
|
||||
signature-verification-failure=warn
|
||||
deprecated-utility=error
|
||||
|
||||
[strict]
|
||||
deprecated-type
|
||||
nonstrict-import
|
||||
sketchy-null
|
||||
unclear-type
|
||||
unsafe-getters-setters
|
||||
untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.92.0
|
||||
^0.105.0
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -74,6 +74,8 @@ clean: ## Cleans dependencies, previous builds and temp files
|
|||
@echo Cleanup finished
|
||||
|
||||
post-install:
|
||||
@./node_modules/.bin/jetify
|
||||
|
||||
@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
|
||||
|
|
|
|||
35
NOTICE.txt
35
NOTICE.txt
|
|
@ -2812,38 +2812,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-syntax-highlighter
|
||||
|
||||
This product contains 'react-native-syntax-highlighter' by Conor Hastings.
|
||||
|
||||
A syntax highlighter for react native using https://github.com/conorhastings/react-syntax-highlighter under the hood
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/conorhastings/react-native-syntax-highlighter#readme
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Conor Hastings
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
|
|||
|
|
@ -74,8 +74,8 @@ import com.android.build.OutputFile
|
|||
|
||||
project.ext.react = [
|
||||
entryFile: "index.js",
|
||||
bundleCommand: "ram-bundle",
|
||||
bundleConfig: "metro.config.js"
|
||||
bundleConfig: "metro.config.js",
|
||||
enableHermes: false,
|
||||
]
|
||||
|
||||
apply from: "../../node_modules/react-native/react.gradle"
|
||||
|
|
@ -87,7 +87,7 @@ if (System.getenv("SENTRY_ENABLED") == "true") {
|
|||
flavorAware: false
|
||||
]
|
||||
|
||||
apply from: "../../node_modules/react-native-sentry/sentry.gradle"
|
||||
apply from: "../../node_modules/@sentry/react-native/sentry.gradle"
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -105,6 +105,28 @@ def enableSeparateBuildPerCPUArchitecture = false
|
|||
*/
|
||||
def enableProguardInReleaseBuilds = false
|
||||
|
||||
/**
|
||||
* The preferred build flavor of JavaScriptCore.
|
||||
*
|
||||
* For example, to use the international variant, you can use:
|
||||
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
|
||||
*
|
||||
* The international variant includes ICU i18n library and necessary data
|
||||
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
|
||||
* give correct results when using with locales other than en-US. Note that
|
||||
* this variant is about 6MiB larger per architecture than default.
|
||||
*/
|
||||
def jscFlavor = 'org.webkit:android-jsc-intl:r241213'
|
||||
|
||||
/**
|
||||
* Whether to enable the Hermes VM.
|
||||
*
|
||||
* This should be set on project.ext.react and mirrored here. If it is not set
|
||||
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
|
||||
* and the benefits of using Hermes will therefore be sharply reduced.
|
||||
*/
|
||||
def enableHermes = project.ext.react.get("enableHermes", false);
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
|
|
@ -113,17 +135,12 @@ android {
|
|||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst '**/libjsc.so'
|
||||
pickFirst '**/libc++_shared.so'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5"
|
||||
versionCode 240
|
||||
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative60"
|
||||
versionCode 241
|
||||
versionName "1.25.0"
|
||||
multiDexEnabled = true
|
||||
ndk {
|
||||
|
|
@ -211,15 +228,21 @@ configurations.all {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
// Make sure to put android-jsc at the top
|
||||
implementation "org.webkit:android-jsc-intl:r241213"
|
||||
if (enableHermes) {
|
||||
def hermesPath = "../../node_modules/hermes-engine/android/";
|
||||
debugImplementation files(hermesPath + "hermes-debug.aar")
|
||||
releaseImplementation files(hermesPath + "hermes-release.aar")
|
||||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
implementation 'com.android.support:design:28.0.0'
|
||||
implementation 'com.android.support:percent:28.0.0'
|
||||
implementation "com.google.firebase:firebase-messaging:17.3.0"
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation "com.google.firebase:firebase-messaging:17.3.0"
|
||||
implementation project(':react-native-document-picker')
|
||||
implementation project(':react-native-keychain')
|
||||
implementation project(':react-native-doc-viewer')
|
||||
|
|
@ -235,22 +258,22 @@ dependencies {
|
|||
implementation project(':react-native-local-auth')
|
||||
implementation project(':jail-monkey')
|
||||
implementation project(':react-native-youtube')
|
||||
implementation project(':react-native-sentry')
|
||||
implementation project(':react-native-exception-handler')
|
||||
implementation project(':rn-fetch-blob')
|
||||
implementation project(':react-native-webview')
|
||||
implementation project(':react-native-gesture-handler')
|
||||
implementation project(':@react-native-community_async-storage')
|
||||
implementation project(':@react-native-community_netinfo')
|
||||
implementation project(':@sentry_react-native')
|
||||
implementation project(':react-native-android-open-settings')
|
||||
implementation project(':react-native-haptic-feedback')
|
||||
|
||||
// For animated GIF support
|
||||
implementation 'com.facebook.fresco:fresco:1.10.0'
|
||||
implementation 'com.facebook.fresco:animated-gif:1.10.0'
|
||||
implementation 'com.facebook.fresco:fresco:2.0.0'
|
||||
implementation 'com.facebook.fresco:animated-gif:2.0.0'
|
||||
// For WebP support, including animated WebP
|
||||
implementation 'com.facebook.fresco:animated-webp:1.10.0'
|
||||
implementation 'com.facebook.fresco:webpsupport:1.10.0'
|
||||
implementation 'com.facebook.fresco:animated-webp:2.0.0'
|
||||
implementation 'com.facebook.fresco:webpsupport:2.0.0'
|
||||
}
|
||||
|
||||
// Run this once to be able to run the application with BUCK
|
||||
|
|
|
|||
7
android/app/proguard-rules.pro
vendored
7
android/app/proguard-rules.pro
vendored
|
|
@ -8,10 +8,3 @@
|
|||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@
|
|||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:launchMode="singleInstance">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
|
@ -52,7 +53,8 @@
|
|||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/AppTheme">
|
||||
android:theme="@style/AppTheme"
|
||||
android:taskAffinity="com.mattermost.share">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.reactnativenavigation.NavigationActivity;
|
||||
|
||||
|
|
@ -10,19 +10,5 @@ public class MainActivity extends NavigationActivity {
|
|||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.launch_screen);
|
||||
|
||||
/**
|
||||
* Reference: https://stackoverflow.com/questions/7944338/resume-last-activity-when-launcher-icon-is-clicked
|
||||
* 1. Open app from launcher/appDrawer
|
||||
* 2. Go home
|
||||
* 3. Send notification and open
|
||||
* 4. It creates a new Activity and Destroys the old
|
||||
* 5. Causing an unnecessary app restart
|
||||
* 6. This solution short-circuits the restart
|
||||
*/
|
||||
if (!isTaskRoot()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.RestrictionsManager;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -22,15 +22,15 @@ import com.wix.reactnativenotifications.RNNotificationsModule;
|
|||
import io.tradle.react.LocalAuthModule;
|
||||
import com.gantix.JailMonkey.JailMonkeyModule;
|
||||
import com.RNFetchBlob.RNFetchBlob;
|
||||
import io.sentry.RNSentryModule;
|
||||
import io.sentry.RNSentryEventEmitter;
|
||||
import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerModule;
|
||||
import com.inprogress.reactnativeyoutube.YouTubeStandaloneModule;
|
||||
import com.reactlibrary.RNReactNativeDocViewerModule;
|
||||
import com.reactnativedocumentpicker.DocumentPicker;
|
||||
import com.philipphecht.RNDocViewerModule;
|
||||
import io.github.elyx0.reactnativedocumentpicker.DocumentPickerModule;
|
||||
import com.oblador.keychain.KeychainModule;
|
||||
import com.reactnativecommunity.asyncstorage.AsyncStorageModule;
|
||||
import com.reactnativecommunity.netinfo.NetInfoModule;
|
||||
import com.reactnativecommunity.webview.RNCWebViewPackage;
|
||||
import io.sentry.RNSentryModule;
|
||||
import com.levelasquez.androidopensettings.AndroidOpenSettings;
|
||||
import com.mkuczera.RNReactNativeHapticFeedbackModule;
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ public class MainApplication extends NavigationApplication implements INotificat
|
|||
case "RNLocalAuth":
|
||||
return new LocalAuthModule(reactContext);
|
||||
case "JailMonkey":
|
||||
return new JailMonkeyModule(reactContext);
|
||||
return new JailMonkeyModule(reactContext, false);
|
||||
case "RNFetchBlob":
|
||||
return new RNFetchBlob(reactContext);
|
||||
case "MattermostManaged":
|
||||
|
|
@ -133,20 +133,18 @@ public class MainApplication extends NavigationApplication implements INotificat
|
|||
return NotificationPreferencesModule.getInstance(instance, reactContext);
|
||||
case "RNTextInputReset":
|
||||
return new RNTextInputResetModule(reactContext);
|
||||
case "RNSentry":
|
||||
return new RNSentryModule(reactContext);
|
||||
case "RNSentryEventEmitter":
|
||||
return new RNSentryEventEmitter(reactContext);
|
||||
case "ReactNativeExceptionHandler":
|
||||
return new ReactNativeExceptionHandlerModule(reactContext);
|
||||
case "YouTubeStandaloneModule":
|
||||
return new YouTubeStandaloneModule(reactContext);
|
||||
case "RNReactNativeDocViewer":
|
||||
return new RNReactNativeDocViewerModule(reactContext);
|
||||
case "RNDocViewer":
|
||||
return new RNDocViewerModule(reactContext);
|
||||
case "RNDocumentPicker":
|
||||
return new DocumentPicker(reactContext);
|
||||
return new DocumentPickerModule(reactContext);
|
||||
case "RNKeychainManager":
|
||||
return new KeychainModule(reactContext);
|
||||
case "RNSentry":
|
||||
return new RNSentryModule(reactContext);
|
||||
case AsyncStorageModule.NAME:
|
||||
return new AsyncStorageModule(reactContext);
|
||||
case NetInfoModule.NAME:
|
||||
|
|
@ -179,13 +177,12 @@ public class MainApplication extends NavigationApplication implements INotificat
|
|||
map.put("RNLocalAuth", new ReactModuleInfo("RNLocalAuth", "io.tradle.react.LocalAuthModule", false, false, false, false, false));
|
||||
map.put("JailMonkey", new ReactModuleInfo("JailMonkey", "com.gantix.JailMonkey.JailMonkeyModule", false, false, true, false, false));
|
||||
map.put("RNFetchBlob", new ReactModuleInfo("RNFetchBlob", "com.RNFetchBlob.RNFetchBlob", false, false, true, false, false));
|
||||
map.put("RNSentry", new ReactModuleInfo("RNSentry", "com.sentry.RNSentryModule", false, false, true, false, false));
|
||||
map.put("RNSentryEventEmitter", new ReactModuleInfo("RNSentryEventEmitter", "com.sentry.RNSentryEventEmitter", false, false, true, false, false));
|
||||
map.put("ReactNativeExceptionHandler", new ReactModuleInfo("ReactNativeExceptionHandler", "com.masteratul.exceptionhandler.ReactNativeExceptionHandlerModule", false, false, false, false, false));
|
||||
map.put("YouTubeStandaloneModule", new ReactModuleInfo("YouTubeStandaloneModule", "com.inprogress.reactnativeyoutube.YouTubeStandaloneModule", false, false, false, false, false));
|
||||
map.put("RNReactNativeDocViewer", new ReactModuleInfo("RNReactNativeDocViewer", "com.reactlibrary.RNReactNativeDocViewerModule", false, false, false, false, false));
|
||||
map.put("RNDocumentPicker", new ReactModuleInfo("RNDocumentPicker", "com.reactnativedocumentpicker.DocumentPicker", false, false, false, false, false));
|
||||
map.put("RNDocViewer", new ReactModuleInfo("RNDocViewer", "com.philipphecht.RNDocViewerModule", false, false, false, false, false));
|
||||
map.put("RNDocumentPicker", new ReactModuleInfo("RNDocumentPicker", "io.github.elyx0.reactnativedocumentpicker.DocumentPickerModule", false, false, false, false, false));
|
||||
map.put("RNKeychainManager", new ReactModuleInfo("RNKeychainManager", "com.oblador.keychain.KeychainModule", false, false, true, false, false));
|
||||
map.put("RNSentry", new ReactModuleInfo("RNSentry", "com.sentry.RNSentryModule", false, false, true, false, false));
|
||||
map.put(AsyncStorageModule.NAME, new ReactModuleInfo(AsyncStorageModule.NAME, "com.reactnativecommunity.asyncstorage.AsyncStorageModule", false, false, false, false, false));
|
||||
map.put(NetInfoModule.NAME, new ReactModuleInfo(NetInfoModule.NAME, "com.reactnativecommunity.netinfo.NetInfoModule", false, false, false, false, false));
|
||||
map.put("RNAndroidOpenSettings", new ReactModuleInfo("RNAndroidOpenSettings", "com.levelasquez.androidopensettings.AndroidOpenSettings", false, false, false, false, false));
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import android.content.Intent;
|
|||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import java.io.IOException;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.support.v13.view.inputmethod.EditorInfoCompat;
|
||||
import android.support.v13.view.inputmethod.InputConnectionCompat;
|
||||
import android.support.v4.os.BuildCompat;
|
||||
import androidx.core.view.inputmethod.EditorInfoCompat;
|
||||
import androidx.core.view.inputmethod.InputConnectionCompat;
|
||||
import androidx.core.os.BuildCompat;
|
||||
import android.text.InputType;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import java.lang.System;
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ public class ShareModule extends ReactContextBaseJavaModule {
|
|||
this.clear();
|
||||
Activity currentActivity = getCurrentActivity();
|
||||
if (currentActivity != null) {
|
||||
currentActivity.finish();
|
||||
currentActivity.finishAndRemoveTask();
|
||||
}
|
||||
|
||||
if (data != null && data.hasKey("url")) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.percent.PercentRelativeLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
|
|
@ -16,4 +16,4 @@
|
|||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/splash" />
|
||||
|
||||
</android.support.percent.PercentRelativeLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.1'
|
||||
classpath 'com.android.tools.build:gradle:3.4.2'
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
|
|
|||
|
|
@ -20,3 +20,6 @@ org.gradle.jvmargs=-Xmx2048M
|
|||
|
||||
#android.enableAapt2=false
|
||||
#android.useDeprecatedNdk=true
|
||||
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-all.zip
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
keystore(
|
||||
name = "debug",
|
||||
properties = "debug.keystore.properties",
|
||||
store = "debug.keystore",
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
)
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
key.store=debug.keystore
|
||||
key.alias=androiddebugkey
|
||||
key.store.password=android
|
||||
key.alias.password=android
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
rootProject.name = 'Mattermost'
|
||||
include ':@sentry_react-native'
|
||||
project(':@sentry_react-native').projectDir = new File(rootProject.projectDir, '../node_modules/@sentry/react-native/android')
|
||||
include ':react-native-android-open-settings'
|
||||
project(':react-native-android-open-settings').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-android-open-settings/android')
|
||||
include ':react-native-haptic-feedback'
|
||||
|
|
@ -15,8 +17,6 @@ include ':react-native-video'
|
|||
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android')
|
||||
include ':react-native-youtube'
|
||||
project(':react-native-youtube').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-youtube/android')
|
||||
include ':react-native-sentry'
|
||||
project(':react-native-sentry').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sentry/android')
|
||||
include ':react-native-exception-handler'
|
||||
project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-exception-handler/android')
|
||||
include ':rn-fetch-blob'
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
|||
import {ViewTypes} from 'app/constants';
|
||||
import {setAppCredentials} from 'app/init/credentials';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import {getDeviceTimezone} from 'app/utils/timezone';
|
||||
import {getDeviceTimezoneAsync} from 'app/utils/timezone';
|
||||
import {setCSRFFromCookie} from 'app/utils/security';
|
||||
|
||||
export function handleLoginIdChanged(loginId) {
|
||||
|
|
@ -51,7 +51,8 @@ export function handleSuccessfulLogin() {
|
|||
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
if (enableTimezone) {
|
||||
dispatch(autoUpdateTimezone(getDeviceTimezone()));
|
||||
const timezone = await getDeviceTimezoneAsync();
|
||||
dispatch(autoUpdateTimezone(timezone));
|
||||
}
|
||||
|
||||
dispatch({
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ exports[`profile_picture_button should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import DeviceInfo from 'react-native-device-info';
|
|||
import AndroidOpenSettings from 'react-native-android-open-settings';
|
||||
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import {DocumentPicker} from 'react-native-document-picker';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import Permissions from 'react-native-permissions';
|
||||
|
||||
|
|
@ -257,13 +257,8 @@ export default class AttachmentButton extends PureComponent {
|
|||
const hasPermission = await this.hasStoragePermission();
|
||||
|
||||
if (hasPermission) {
|
||||
DocumentPicker.show({
|
||||
filetype: [browseFileTypes],
|
||||
}, async (error, res) => {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await DocumentPicker.pick({type: [browseFileTypes]});
|
||||
if (Platform.OS === 'android') {
|
||||
// For android we need to retrieve the realPath in case the file being imported is from the cloud
|
||||
const newUri = await ShareExtension.getFilePath(res.uri);
|
||||
|
|
@ -278,7 +273,9 @@ export default class AttachmentButton extends PureComponent {
|
|||
res.uri = decodeURIComponent(res.uri);
|
||||
|
||||
this.uploadFiles([res]);
|
||||
});
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,6 @@ import AttachmentButton from './index';
|
|||
|
||||
jest.mock('react-intl');
|
||||
|
||||
jest.mock('Platform', () => {
|
||||
const Platform = require.requireActual('Platform');
|
||||
Platform.OS = 'ios';
|
||||
return Platform;
|
||||
});
|
||||
|
||||
describe('AttachmentButton', () => {
|
||||
const formatMessage = jest.fn();
|
||||
const baseProps = {
|
||||
|
|
|
|||
|
|
@ -59,19 +59,19 @@ export default class Badge extends PureComponent {
|
|||
this.mounted = false;
|
||||
}
|
||||
|
||||
setBadgeRef = (ref) => {
|
||||
this.badgeRef = ref;
|
||||
};
|
||||
|
||||
handlePress = () => {
|
||||
if (this.props.onPress) {
|
||||
this.props.onPress();
|
||||
}
|
||||
};
|
||||
|
||||
setBadgeRef = (ref) => {
|
||||
this.badgeContainerRef = ref;
|
||||
};
|
||||
|
||||
setNativeProps = (props) => {
|
||||
if (this.mounted && this.badgeContainerRef) {
|
||||
this.badgeContainerRef.setNativeProps(props);
|
||||
if (this.mounted && this.badgeRef) {
|
||||
this.badgeRef.setNativeProps(props);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ exports[`UserListRow should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -105,6 +106,7 @@ exports[`UserListRow should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -202,6 +204,7 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -233,6 +236,7 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -332,6 +336,7 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -363,6 +368,7 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -473,6 +479,7 @@ exports[`UserListRow should match snapshot for guest user 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -504,6 +511,7 @@ exports[`UserListRow should match snapshot for guest user 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default class EmojiPicker extends EmojiPickerBase {
|
|||
<React.Fragment>
|
||||
<View style={styles.searchBar}>
|
||||
<SearchBar
|
||||
ref={this.searchBarRef}
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export default class EmojiPicker extends EmojiPickerBase {
|
|||
>
|
||||
<View style={[styles.searchBar, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref={this.searchBarRef}
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
|
|
@ -71,7 +71,6 @@ export default class EmojiPicker extends EmojiPickerBase {
|
|||
{this.renderListComponent(shorten)}
|
||||
{!searchTerm &&
|
||||
<KeyboardTrackingView
|
||||
ref={this.keyboardTracker}
|
||||
scrollViewNativeID={SCROLLVIEW_NATIVE_ID}
|
||||
normalList={true}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@ export default class EmojiPicker extends PureComponent {
|
|||
const emojis = this.renderableEmojis(props.emojisBySection, props.deviceWidth);
|
||||
const emojiSectionIndexByOffset = this.measureEmojiSections(emojis);
|
||||
|
||||
this.searchBarRef = React.createRef();
|
||||
this.scrollToSectionTries = 0;
|
||||
this.state = {
|
||||
emojis,
|
||||
|
|
@ -97,8 +96,8 @@ export default class EmojiPicker extends PureComponent {
|
|||
if (this.props.deviceWidth !== nextProps.deviceWidth) {
|
||||
this.rebuildEmojis = true;
|
||||
|
||||
if (this.searchBarRef?.current) {
|
||||
this.searchBarRef.current.blur();
|
||||
if (this.searchBarRef) {
|
||||
this.searchBarRef.blur();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,6 +107,14 @@ export default class EmojiPicker extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setSearchBarRef = (ref) => {
|
||||
this.searchBarRef = ref;
|
||||
}
|
||||
|
||||
setSectionListRef = (ref) => {
|
||||
this.sectionListRef = ref;
|
||||
};
|
||||
|
||||
setRebuiltEmojis = (searchBarAnimationComplete = true) => {
|
||||
if (this.rebuildEmojis && searchBarAnimationComplete) {
|
||||
this.rebuildEmojis = false;
|
||||
|
|
@ -253,6 +260,7 @@ export default class EmojiPicker extends PureComponent {
|
|||
} else {
|
||||
listComponent = (
|
||||
<SectionList
|
||||
ref={this.setSectionListRef}
|
||||
getItemLayout={this.sectionListGetItemLayout}
|
||||
initialNumToRender={50}
|
||||
keyboardShouldPersistTaps='always'
|
||||
|
|
@ -265,7 +273,6 @@ export default class EmojiPicker extends PureComponent {
|
|||
onScroll={this.onScroll}
|
||||
onScrollToIndexFailed={this.handleScrollToSectionFailed}
|
||||
pageSize={50}
|
||||
ref={this.attachSectionList}
|
||||
removeClippedSubviews={false}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
|
|
@ -356,7 +363,7 @@ export default class EmojiPicker extends PureComponent {
|
|||
jumpToSection: true,
|
||||
currentSectionIndex: index,
|
||||
}, () => {
|
||||
this.sectionList.scrollToLocation({
|
||||
this.sectionListRef.scrollToLocation({
|
||||
sectionIndex: index,
|
||||
itemIndex: 0,
|
||||
viewOffset: 25,
|
||||
|
|
@ -423,10 +430,6 @@ export default class EmojiPicker extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
attachSectionList = (c) => {
|
||||
this.sectionList = c;
|
||||
};
|
||||
|
||||
renderFooter = () => {
|
||||
if (!this.state.missingPages) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ exports[`FileAttachment should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ exports[`PostAttachmentOpenGraph should match snapshot with a single image file
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export default class FileAttachmentDocument extends PureComponent {
|
|||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.eventEmitter = new NativeEventEmitter(NativeModules.RNReactNativeDocViewer);
|
||||
this.eventEmitter = new NativeEventEmitter(NativeModules.RNDocViewer);
|
||||
this.eventEmitter.addListener('DoneButtonEvent', this.onDonePreviewingFile);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,22 +5,19 @@ import {PropTypes} from 'prop-types';
|
|||
import React from 'react';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Platform,
|
||||
Clipboard,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import SyntaxHighlighter from 'react-native-syntax-highlighter';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
import {getDisplayNameForLanguage, getCodeFont} from 'app/utils/markdown';
|
||||
import {getDisplayNameForLanguage} from 'app/utils/markdown';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme, getHighlightStyleFromTheme} from 'app/utils/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
|
|
@ -43,13 +40,11 @@ export default class MarkdownCodeBlock extends React.PureComponent {
|
|||
};
|
||||
|
||||
handlePress = preventDoubleTap(() => {
|
||||
const {language, content, textStyle} = this.props;
|
||||
const {language, content} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'Code';
|
||||
const passProps = {
|
||||
content,
|
||||
language,
|
||||
textStyle,
|
||||
};
|
||||
|
||||
const languageDisplayName = getDisplayNameForLanguage(language);
|
||||
|
|
@ -155,26 +150,6 @@ export default class MarkdownCodeBlock extends React.PureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
let textComponent = null;
|
||||
if (Platform.OS === 'ios') {
|
||||
textComponent = (
|
||||
<SyntaxHighlighter
|
||||
language={this.props.language}
|
||||
style={getHighlightStyleFromTheme(this.props.theme)}
|
||||
highlighter={'hljs'}
|
||||
customStyle={{...style.codeText, ...this.props.textStyle}}
|
||||
>
|
||||
{content}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
} else {
|
||||
textComponent = (
|
||||
<Text style={[style.codeText, this.props.textStyle]}>
|
||||
{content}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.handlePress}
|
||||
|
|
@ -189,7 +164,9 @@ export default class MarkdownCodeBlock extends React.PureComponent {
|
|||
</View>
|
||||
<View style={style.rightColumn}>
|
||||
<View style={style.code}>
|
||||
{textComponent}
|
||||
<Text style={[style.codeText, this.props.textStyle]}>
|
||||
{content}
|
||||
</Text>
|
||||
</View>
|
||||
{plusMoreLines}
|
||||
</View>
|
||||
|
|
@ -227,15 +204,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
paddingHorizontal: 6,
|
||||
...Platform.select({
|
||||
ios: {
|
||||
paddingVertical: 0,
|
||||
backgroundColor: getHighlightStyleFromTheme(theme).hljs.background,
|
||||
},
|
||||
android: {
|
||||
paddingVertical: 4,
|
||||
},
|
||||
}),
|
||||
paddingVertical: 4,
|
||||
},
|
||||
code: {
|
||||
flexDirection: 'row',
|
||||
|
|
@ -245,12 +214,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
color: changeOpacity(theme.centerChannelColor, 0.65),
|
||||
fontSize: 12,
|
||||
lineHeight: 18,
|
||||
...Platform.select({
|
||||
ios: {
|
||||
fontFamily: getCodeFont(),
|
||||
paddingVertical: 6,
|
||||
},
|
||||
}),
|
||||
},
|
||||
plusMoreLinesText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.4),
|
||||
|
|
|
|||
|
|
@ -92,6 +92,14 @@ export default class MarkdownImage extends React.Component {
|
|||
this.mounted = false;
|
||||
}
|
||||
|
||||
setImageRef = (ref) => {
|
||||
this.imageRef = ref;
|
||||
}
|
||||
|
||||
setItemRef = (ref) => {
|
||||
this.itemRef = ref;
|
||||
}
|
||||
|
||||
getSource = () => {
|
||||
let source = this.props.source;
|
||||
|
||||
|
|
@ -195,7 +203,7 @@ export default class MarkdownImage extends React.Component {
|
|||
},
|
||||
}];
|
||||
|
||||
previewImageAtIndex([this.refs.item], 0, files);
|
||||
previewImageAtIndex([this.itemRef], 0, files);
|
||||
};
|
||||
|
||||
loadImageSize = (source) => {
|
||||
|
|
@ -252,7 +260,7 @@ export default class MarkdownImage extends React.Component {
|
|||
style={{width, height}}
|
||||
>
|
||||
<ProgressiveImage
|
||||
ref='image'
|
||||
ref={this.setImageRef}
|
||||
defaultSource={source}
|
||||
resizeMode='contain'
|
||||
style={{width, height}}
|
||||
|
|
@ -282,7 +290,7 @@ export default class MarkdownImage extends React.Component {
|
|||
|
||||
return (
|
||||
<View
|
||||
ref='item'
|
||||
ref={this.setItemRef}
|
||||
style={style.container}
|
||||
>
|
||||
{image}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,14 @@ export default class AttachmentImage extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setImageRef = (ref) => {
|
||||
this.imageRef = ref;
|
||||
}
|
||||
|
||||
setItemRef = (ref) => {
|
||||
this.itemRef = ref;
|
||||
}
|
||||
|
||||
handlePreviewImage = () => {
|
||||
const {imageUrl} = this.props;
|
||||
const {
|
||||
|
|
@ -79,7 +87,7 @@ export default class AttachmentImage extends PureComponent {
|
|||
localPath: uri,
|
||||
},
|
||||
}];
|
||||
previewImageAtIndex([this.refs.item], 0, files);
|
||||
previewImageAtIndex([this.itemRef], 0, files);
|
||||
};
|
||||
|
||||
setImageDimensions = (imageUri, dimensions, originalWidth, originalHeight) => {
|
||||
|
|
@ -132,7 +140,7 @@ export default class AttachmentImage extends PureComponent {
|
|||
if (imageUri) {
|
||||
progressiveImage = (
|
||||
<ProgressiveImage
|
||||
ref='image'
|
||||
ref={this.setImageRef}
|
||||
style={{height, width}}
|
||||
imageUri={imageUri}
|
||||
resizeMode='contain'
|
||||
|
|
@ -149,7 +157,7 @@ export default class AttachmentImage extends PureComponent {
|
|||
type={'none'}
|
||||
>
|
||||
<View
|
||||
ref='item'
|
||||
ref={this.setItemRef}
|
||||
style={[style.imageContainer, {width, height}]}
|
||||
>
|
||||
{progressiveImage}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,14 @@
|
|||
/* eslint-disable no-underscore-dangle */
|
||||
|
||||
import React from 'react';
|
||||
import {TextInput, Text, TouchableWithoutFeedback} from 'react-native';
|
||||
import UIManager from 'UIManager';
|
||||
import {
|
||||
TextInput,
|
||||
Text,
|
||||
TouchableWithoutFeedback,
|
||||
UIManager,
|
||||
requireNativeComponent,
|
||||
} from 'react-native';
|
||||
import invariant from 'invariant';
|
||||
import requireNativeComponent from 'requireNativeComponent';
|
||||
|
||||
const AndroidTextInput = requireNativeComponent('PasteableTextInputAndroid');
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
this.mounted = false;
|
||||
}
|
||||
|
||||
setItemRef = (ref) => {
|
||||
this.itemRef = ref;
|
||||
}
|
||||
|
||||
fetchData = (url, openGraphData) => {
|
||||
if (!openGraphData) {
|
||||
this.props.actions.getOpenGraphMetadata(url);
|
||||
|
|
@ -205,7 +209,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
},
|
||||
}];
|
||||
|
||||
previewImageAtIndex([this.refs.item], 0, files);
|
||||
previewImageAtIndex([this.itemRef], 0, files);
|
||||
};
|
||||
|
||||
renderDescription = () => {
|
||||
|
|
@ -247,7 +251,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
|
||||
return (
|
||||
<View
|
||||
ref='item'
|
||||
ref={this.setItemRef}
|
||||
style={[style.imageContainer, {width, height}]}
|
||||
>
|
||||
<TouchableWithFeedback
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renderSystemMessage uses renderer for Channel Display Name update 1`] = `
|
||||
<Connect(Markdown)
|
||||
baseTextStyle={Object {}}
|
||||
onPostPress={[MockFunction]}
|
||||
textStyles={Object {}}
|
||||
value="{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renderSystemMessage uses renderer for Channel Header update 1`] = `
|
||||
<Connect(Markdown)
|
||||
baseTextStyle={Object {}}
|
||||
onPostPress={[MockFunction]}
|
||||
textStyles={Object {}}
|
||||
value="{username} updated the channel header from: {oldHeader} to: {newHeader}"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renderSystemMessage uses renderer for Channel Purpose update 1`] = `
|
||||
<Connect(Markdown)
|
||||
baseTextStyle={Object {}}
|
||||
onPostPress={[MockFunction]}
|
||||
textStyles={Object {}}
|
||||
value="{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renderSystemMessage uses renderer for archived channel 1`] = `
|
||||
<Connect(Markdown)
|
||||
baseTextStyle={Object {}}
|
||||
onPostPress={[MockFunction]}
|
||||
textStyles={Object {}}
|
||||
value="{username} archived the channel"
|
||||
/>
|
||||
`;
|
||||
|
|
@ -13,6 +13,7 @@ import Icon from 'react-native-vector-icons/Ionicons';
|
|||
import {Posts} from 'mattermost-redux/constants';
|
||||
|
||||
import CombinedSystemMessage from 'app/components/combined_system_message';
|
||||
import {renderSystemMessage} from './system_message_helpers';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Markdown from 'app/components/markdown';
|
||||
import MarkdownEmoji from 'app/components/markdown/markdown_emoji';
|
||||
|
|
@ -348,6 +349,13 @@ export default class PostBody extends PureComponent {
|
|||
const messageStyle = isSystemMessage ? [style.message, style.systemMessage] : style.message;
|
||||
const isPendingOrFailedPost = isPending || isFailed;
|
||||
|
||||
const messageStyles = {messageStyle, textStyles};
|
||||
const intl = this.context.intl;
|
||||
const systemMessage = renderSystemMessage(this.props, messageStyles, intl);
|
||||
if (systemMessage) {
|
||||
return systemMessage;
|
||||
}
|
||||
|
||||
let body;
|
||||
let messageComponent;
|
||||
if (hasBeenDeleted) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import PostBodyAdditionalContent from 'app/components/post_body_additional_conte
|
|||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import PostBody from './post_body.js';
|
||||
import * as SystemMessageHelpers from './system_message_helpers';
|
||||
|
||||
jest.mock('./system_message_helpers');
|
||||
|
||||
describe('PostBody', () => {
|
||||
const baseProps = {
|
||||
|
|
@ -122,4 +125,9 @@ describe('PostBody', () => {
|
|||
instance.measurePost(event);
|
||||
expect(wrapper.state('isLongPost')).toEqual(false);
|
||||
});
|
||||
|
||||
test('should return system message as post body', () => {
|
||||
shallowWithIntl(<PostBody {...baseProps}/>);
|
||||
expect(SystemMessageHelpers.renderSystemMessage.mock.calls.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
160
app/components/post_body/system_message_helpers.js
Normal file
160
app/components/post_body/system_message_helpers.js
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
import Markdown from 'app/components/markdown';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
const renderUsername = (value) => {
|
||||
return (value[0] === '@') ? value : `@${value}`;
|
||||
};
|
||||
|
||||
const renderMessage = (postBodyProps, styles, intl, localeHolder, values) => {
|
||||
const {onPress} = postBodyProps;
|
||||
const {messageStyle, textStyles} = styles;
|
||||
|
||||
return (
|
||||
<Markdown
|
||||
baseTextStyle={messageStyle}
|
||||
onPostPress={onPress}
|
||||
textStyles={textStyles}
|
||||
value={intl.formatMessage(localeHolder, values)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderHeaderChangeMessage = (postBodyProps, styles, intl) => {
|
||||
const {postProps} = postBodyProps;
|
||||
let values;
|
||||
|
||||
if (!postProps.username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const username = renderUsername(postProps.username);
|
||||
const oldHeader = postProps.old_header;
|
||||
const newHeader = postProps.new_header;
|
||||
let localeHolder;
|
||||
|
||||
if (postProps.new_header) {
|
||||
if (postProps.old_header) {
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_header_message_and_forget.updated_from'),
|
||||
defaultMessage: '{username} updated the channel header from: {oldHeader} to: {newHeader}',
|
||||
};
|
||||
|
||||
values = {username, oldHeader, newHeader};
|
||||
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
|
||||
}
|
||||
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_header_message_and_forget.updated_to'),
|
||||
defaultMessage: '{username} updated the channel header to: {newHeader}',
|
||||
};
|
||||
|
||||
values = {username, oldHeader, newHeader};
|
||||
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
|
||||
} else if (postProps.old_header) {
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_header_message_and_forget.removed'),
|
||||
defaultMessage: '{username} removed the channel header (was: {oldHeader})',
|
||||
};
|
||||
|
||||
values = {username, oldHeader, newHeader};
|
||||
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderPurposeChangeMessage = (postBodyProps, styles, intl) => {
|
||||
const {postProps} = postBodyProps;
|
||||
let values;
|
||||
|
||||
if (!postProps.username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const username = renderUsername(postProps.username);
|
||||
const oldPurpose = postProps.old_purpose;
|
||||
const newPurpose = postProps.new_purpose;
|
||||
let localeHolder;
|
||||
|
||||
if (postProps.new_purpose) {
|
||||
if (postProps.old_purpose) {
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_purpose_message.updated_from'),
|
||||
defaultMessage: '{username} updated the channel purpose from: {oldPurpose} to: {newPurpose}',
|
||||
};
|
||||
|
||||
values = {username, oldPurpose, newPurpose};
|
||||
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
|
||||
}
|
||||
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_purpose_message.updated_to'),
|
||||
defaultMessage: '{username} updated the channel purpose to: {newPurpose}',
|
||||
};
|
||||
|
||||
values = {username, oldPurpose, newPurpose};
|
||||
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
|
||||
} else if (postProps.old_purpose) {
|
||||
localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_purpose_message.removed'),
|
||||
defaultMessage: '{username} removed the channel purpose (was: {oldPurpose})',
|
||||
};
|
||||
|
||||
values = {username, oldPurpose, newPurpose};
|
||||
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderDisplayNameChangeMessage = (postBodyProps, styles, intl) => {
|
||||
const {postProps} = postBodyProps;
|
||||
const oldDisplayName = postProps.old_displayname;
|
||||
const newDisplayName = postProps.new_displayname;
|
||||
|
||||
if (!(postProps.username && postProps.old_displayname && postProps.new_displayname)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const username = renderUsername(postProps.username);
|
||||
const localeHolder = {
|
||||
id: t('mobile.system_message.update_channel_displayname_message_and_forget.updated_from'),
|
||||
defaultMessage: '{username} updated the channel display name from: {oldDisplayName} to: {newDisplayName}',
|
||||
};
|
||||
|
||||
const values = {username, oldDisplayName, newDisplayName};
|
||||
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
|
||||
};
|
||||
|
||||
const renderArchivedMessage = (postBodyProps, styles, intl) => {
|
||||
const {postProps} = postBodyProps;
|
||||
|
||||
const username = renderUsername(postProps.username);
|
||||
const localeHolder = {
|
||||
id: t('mobile.system_message.channel_archived_message'),
|
||||
defaultMessage: '{username} archived the channel',
|
||||
};
|
||||
|
||||
const values = {username};
|
||||
return renderMessage(postBodyProps, styles, intl, localeHolder, values);
|
||||
};
|
||||
|
||||
const systemMessageRenderers = {
|
||||
[Posts.POST_TYPES.HEADER_CHANGE]: renderHeaderChangeMessage,
|
||||
[Posts.POST_TYPES.DISPLAYNAME_CHANGE]: renderDisplayNameChangeMessage,
|
||||
[Posts.POST_TYPES.PURPOSE_CHANGE]: renderPurposeChangeMessage,
|
||||
[Posts.POST_TYPES.CHANNEL_DELETED]: renderArchivedMessage,
|
||||
};
|
||||
|
||||
export const renderSystemMessage = (postBodyProps, styles, intl) => {
|
||||
const renderer = systemMessageRenderers[postBodyProps.postType];
|
||||
if (!renderer) {
|
||||
return null;
|
||||
}
|
||||
return renderer(postBodyProps, styles, intl);
|
||||
};
|
||||
90
app/components/post_body/system_message_helpers.test.js
Normal file
90
app/components/post_body/system_message_helpers.test.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import * as SystemMessageHelpers from './system_message_helpers';
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
|
||||
const basePostBodyProps = {
|
||||
postProps: {
|
||||
username: 'username',
|
||||
},
|
||||
onPress: jest.fn(),
|
||||
};
|
||||
|
||||
const mockStyles = {
|
||||
messageStyle: {},
|
||||
textStyles: {},
|
||||
};
|
||||
|
||||
const mockIntl = {
|
||||
formatMessage: ({defaultMessage}) => defaultMessage,
|
||||
};
|
||||
|
||||
describe('renderSystemMessage', () => {
|
||||
test('uses renderer for Channel Header update', () => {
|
||||
const postBodyProps = {
|
||||
...basePostBodyProps,
|
||||
postProps: {
|
||||
...basePostBodyProps.postProps,
|
||||
old_header: 'old header',
|
||||
new_header: 'new header',
|
||||
},
|
||||
postType: Posts.POST_TYPES.HEADER_CHANGE,
|
||||
};
|
||||
const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
|
||||
expect(renderedMessage).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('uses renderer for Channel Display Name update', () => {
|
||||
const postBodyProps = {
|
||||
...basePostBodyProps,
|
||||
postProps: {
|
||||
...basePostBodyProps.postProps,
|
||||
old_displayname: 'old displayname',
|
||||
new_displayname: 'new displayname',
|
||||
},
|
||||
postType: Posts.POST_TYPES.DISPLAYNAME_CHANGE,
|
||||
};
|
||||
|
||||
const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
|
||||
expect(renderedMessage).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('uses renderer for Channel Purpose update', () => {
|
||||
const postBodyProps = {
|
||||
...basePostBodyProps,
|
||||
postProps: {
|
||||
...basePostBodyProps.postProps,
|
||||
old_purpose: 'old purpose',
|
||||
new_purpose: 'new purpose',
|
||||
},
|
||||
postType: Posts.POST_TYPES.PURPOSE_CHANGE,
|
||||
};
|
||||
|
||||
const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
|
||||
expect(renderedMessage).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('uses renderer for archived channel', () => {
|
||||
const postBodyProps = {
|
||||
...basePostBodyProps,
|
||||
postProps: {
|
||||
...basePostBodyProps.postProps,
|
||||
},
|
||||
postType: Posts.POST_TYPES.CHANNEL_DELETED,
|
||||
};
|
||||
|
||||
const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
|
||||
expect(renderedMessage).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('is null for non-qualifying system messages', () => {
|
||||
const postBodyProps = {
|
||||
...basePostBodyProps,
|
||||
postType: 'not_relevant',
|
||||
};
|
||||
|
||||
const renderedMessage = SystemMessageHelpers.renderSystemMessage(postBodyProps, mockStyles, mockIntl);
|
||||
expect(renderedMessage).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,509 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PostHeader should match snapshot when just a base post 1`] = `
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"marginTop": 10,
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableWithFeedbackIOS
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": 3,
|
||||
"marginRight": 5,
|
||||
"maxWidth": "60%",
|
||||
},
|
||||
null,
|
||||
]
|
||||
}
|
||||
type="opacity"
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flexGrow": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"paddingVertical": 2,
|
||||
}
|
||||
}
|
||||
>
|
||||
John Smith
|
||||
</Text>
|
||||
</TouchableWithFeedbackIOS>
|
||||
<FormattedTime
|
||||
hour12={true}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flex": 1,
|
||||
"fontSize": 12,
|
||||
"marginTop": 5,
|
||||
"opacity": 0.5,
|
||||
}
|
||||
}
|
||||
timeZone=""
|
||||
value={0}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
exports[`PostHeader should match snapshot when post is autoresponder 1`] = `
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"marginTop": 10,
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": 3,
|
||||
"marginRight": 5,
|
||||
"maxWidth": "60%",
|
||||
},
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flexGrow": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"paddingVertical": 2,
|
||||
}
|
||||
}
|
||||
>
|
||||
John Smith
|
||||
</Text>
|
||||
</View>
|
||||
<Tag
|
||||
defaultMessage="AUTOMATIC REPLY"
|
||||
id="post_info.auto_responder"
|
||||
show={true}
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": 5,
|
||||
"marginLeft": 0,
|
||||
"marginRight": 5,
|
||||
}
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<FormattedTime
|
||||
hour12={true}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flex": 1,
|
||||
"fontSize": 12,
|
||||
"marginTop": 5,
|
||||
"opacity": 0.5,
|
||||
}
|
||||
}
|
||||
timeZone=""
|
||||
value={0}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
exports[`PostHeader should match snapshot when post is from system message 1`] = `
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"marginTop": 10,
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": 3,
|
||||
"marginRight": 5,
|
||||
"maxWidth": "60%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedText
|
||||
defaultMessage="System"
|
||||
id="post_info.system"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flexGrow": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"paddingVertical": 2,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<FormattedTime
|
||||
hour12={true}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flex": 1,
|
||||
"fontSize": 12,
|
||||
"marginTop": 5,
|
||||
"opacity": 0.5,
|
||||
}
|
||||
}
|
||||
timeZone=""
|
||||
value={0}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
exports[`PostHeader should match snapshot when post isBot and shouldRenderReplyButton 1`] = `
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"marginTop": 10,
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableWithFeedbackIOS
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": 3,
|
||||
"marginRight": 5,
|
||||
"maxWidth": "60%",
|
||||
},
|
||||
Object {
|
||||
"maxWidth": "50%",
|
||||
},
|
||||
]
|
||||
}
|
||||
type="opacity"
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flexGrow": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"paddingVertical": 2,
|
||||
}
|
||||
}
|
||||
>
|
||||
John Smith
|
||||
</Text>
|
||||
</TouchableWithFeedbackIOS>
|
||||
<BotTag
|
||||
style={
|
||||
Object {
|
||||
"marginBottom": 5,
|
||||
"marginLeft": 0,
|
||||
"marginRight": 5,
|
||||
}
|
||||
}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<FormattedTime
|
||||
hour12={true}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flex": 1,
|
||||
"fontSize": 12,
|
||||
"marginTop": 5,
|
||||
"opacity": 0.5,
|
||||
}
|
||||
}
|
||||
timeZone=""
|
||||
value={0}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"justifyContent": "flex-end",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableWithFeedbackIOS
|
||||
onPress={[MockFunction]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "flex-start",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"minWidth": 40,
|
||||
"paddingBottom": 10,
|
||||
"paddingTop": 2,
|
||||
}
|
||||
}
|
||||
type="opacity"
|
||||
>
|
||||
<ReplyIcon
|
||||
color="#2389d7"
|
||||
height={16}
|
||||
width={16}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#2389d7",
|
||||
"fontSize": 12,
|
||||
"marginLeft": 2,
|
||||
"marginTop": 2,
|
||||
}
|
||||
}
|
||||
>
|
||||
0
|
||||
</Text>
|
||||
</TouchableWithFeedbackIOS>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
exports[`PostHeader should match snapshot when post should display reply button 1`] = `
|
||||
<React.Fragment>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"marginTop": 10,
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableWithFeedbackIOS
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": 3,
|
||||
"marginRight": 5,
|
||||
"maxWidth": "60%",
|
||||
},
|
||||
null,
|
||||
]
|
||||
}
|
||||
type="opacity"
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flexGrow": 1,
|
||||
"fontSize": 15,
|
||||
"fontWeight": "600",
|
||||
"paddingVertical": 2,
|
||||
}
|
||||
}
|
||||
>
|
||||
John Smith
|
||||
</Text>
|
||||
</TouchableWithFeedbackIOS>
|
||||
<FormattedTime
|
||||
hour12={true}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"flex": 1,
|
||||
"fontSize": 12,
|
||||
"marginTop": 5,
|
||||
"opacity": 0.5,
|
||||
}
|
||||
}
|
||||
timeZone=""
|
||||
value={0}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"justifyContent": "flex-end",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableWithFeedbackIOS
|
||||
onPress={[MockFunction]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "flex-start",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-end",
|
||||
"minWidth": 40,
|
||||
"paddingBottom": 10,
|
||||
"paddingTop": 2,
|
||||
}
|
||||
}
|
||||
type="opacity"
|
||||
>
|
||||
<ReplyIcon
|
||||
color="#2389d7"
|
||||
height={16}
|
||||
width={16}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#2389d7",
|
||||
"fontSize": 12,
|
||||
"marginLeft": 2,
|
||||
"marginTop": 2,
|
||||
}
|
||||
}
|
||||
>
|
||||
0
|
||||
</Text>
|
||||
</TouchableWithFeedbackIOS>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
|
@ -117,9 +117,16 @@ export default class PostHeader extends PureComponent {
|
|||
fromAutoResponder,
|
||||
overrideUsername,
|
||||
theme,
|
||||
renderReplies,
|
||||
shouldRenderReplyButton,
|
||||
commentedOnDisplayName,
|
||||
commentCount,
|
||||
isBot,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
const showReply = shouldRenderReplyButton || (!commentedOnDisplayName && commentCount > 0 && renderReplies);
|
||||
const displayNameStyle = [style.displayNameContainer, showReply && (isBot || fromAutoResponder || fromWebHook) ? style.displayNameContainerBotReplyWidth : null];
|
||||
|
||||
if (fromAutoResponder || fromWebHook) {
|
||||
let name = displayName;
|
||||
|
|
@ -128,7 +135,7 @@ export default class PostHeader extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<View style={[style.displayNameContainer, {maxWidth: fromAutoResponder ? '30%' : '60%'}]}>
|
||||
<View style={displayNameStyle}>
|
||||
<Text
|
||||
style={style.displayName}
|
||||
ellipsizeMode={'tail'}
|
||||
|
|
@ -152,7 +159,7 @@ export default class PostHeader extends PureComponent {
|
|||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.handleUsernamePress}
|
||||
style={style.displayNameContainer}
|
||||
style={displayNameStyle}
|
||||
type={'opacity'}
|
||||
>
|
||||
<Text
|
||||
|
|
@ -357,5 +364,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
marginBottom: 3,
|
||||
lineHeight: 21,
|
||||
},
|
||||
displayNameContainerBotReplyWidth: {
|
||||
maxWidth: '50%',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
98
app/components/post_header/post_header.test.js
Normal file
98
app/components/post_header/post_header.test.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
// 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 PostHeader from './post_header';
|
||||
|
||||
describe('PostHeader', () => {
|
||||
const baseProps = {
|
||||
commentCount: 0,
|
||||
commentedOnDisplayName: '',
|
||||
createAt: 0,
|
||||
displayName: 'John Smith',
|
||||
enablePostUsernameOverride: false,
|
||||
fromWebHook: false,
|
||||
isPendingOrFailedPost: false,
|
||||
isSearchResult: false,
|
||||
isSystemMessage: false,
|
||||
fromAutoResponder: false,
|
||||
militaryTime: false,
|
||||
overrideUsername: '',
|
||||
renderReplies: false,
|
||||
shouldRenderReplyButton: false,
|
||||
showFullDate: false,
|
||||
theme: Preferences.THEMES.default,
|
||||
username: 'JohnSmith',
|
||||
isBot: false,
|
||||
isGuest: false,
|
||||
userTimezone: '',
|
||||
enableTimezone: false,
|
||||
previousPostExists: false,
|
||||
post: {id: 'post'},
|
||||
beforePrevPostUserId: '0',
|
||||
onPress: jest.fn(),
|
||||
};
|
||||
|
||||
test('should match snapshot when just a base post', () => {
|
||||
const wrapper = shallow(
|
||||
<PostHeader {...baseProps}/>
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
expect(wrapper.find('#ReplyIcon').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
test('should match snapshot when post isBot and shouldRenderReplyButton', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
shouldRenderReplyButton: true,
|
||||
isBot: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<PostHeader {...props}/>
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when post should display reply button', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
shouldRenderReplyButton: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<PostHeader {...props}/>
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when post is autoresponder', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
fromAutoResponder: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<PostHeader {...props}/>
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
expect(wrapper.find('#ReplyIcon').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
test('should match snapshot when post is from system message', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
isSystemMessage: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(
|
||||
<PostHeader {...props}/>
|
||||
);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
expect(wrapper.find('#ReplyIcon').exists()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -44,6 +44,7 @@ exports[`PostTextBox should match, full snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -122,6 +123,7 @@ exports[`PostTextBox should match, full snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -16,14 +16,6 @@ import EphemeralStore from 'app/store/ephemeral_store';
|
|||
|
||||
import PostTextbox from './post_textbox.ios';
|
||||
|
||||
jest.mock('NativeEventEmitter');
|
||||
|
||||
jest.mock('Alert', () => {
|
||||
return {
|
||||
alert: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('PostTextBox', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
|
|
|
|||
|
|
@ -409,8 +409,8 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleUploadFiles = (images) => {
|
||||
this.props.actions.initUploadFiles(images, this.props.rootId);
|
||||
handleUploadFiles = (files) => {
|
||||
this.props.actions.initUploadFiles(files, this.props.rootId);
|
||||
};
|
||||
|
||||
isFileLoading = () => {
|
||||
|
|
@ -685,21 +685,21 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
showPasteImageErrorDialog = () => {
|
||||
showPasteFilesErrorDialog = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
Alert.alert(
|
||||
formatMessage({
|
||||
id: 'mobile.image_paste.error_title',
|
||||
defaultMessage: 'Paste Image failed',
|
||||
id: 'mobile.files_paste.error_title',
|
||||
defaultMessage: 'Paste failed',
|
||||
}),
|
||||
formatMessage({
|
||||
id: 'mobile.image_paste.error_description',
|
||||
defaultMessage: 'An error occurred while pasting the image. Please try again.',
|
||||
id: 'mobile.files_paste.error_description',
|
||||
defaultMessage: 'An error occurred while pasting the file(s). Please try again.',
|
||||
}),
|
||||
[
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'mobile.image_paste.error_dismiss',
|
||||
id: 'mobile.files_paste.error_dismiss',
|
||||
defaultMessage: 'Dismiss',
|
||||
}),
|
||||
},
|
||||
|
|
@ -707,27 +707,27 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
handlePasteImages = (error, images) => {
|
||||
handlePasteFiles = (error, files) => {
|
||||
if (this.props.screenId === EphemeralStore.getNavigationTopComponentId()) {
|
||||
if (error) {
|
||||
this.showPasteImageErrorDialog();
|
||||
this.showPasteFilesErrorDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
const {maxFileSize, files} = this.props;
|
||||
const availableCount = MAX_FILE_COUNT - files.length;
|
||||
if (images.length > availableCount) {
|
||||
const {maxFileSize} = this.props;
|
||||
const availableCount = MAX_FILE_COUNT - this.props.files.length;
|
||||
if (files.length > availableCount) {
|
||||
this.onShowFileMaxWarning();
|
||||
return;
|
||||
}
|
||||
|
||||
const largeImage = images.find((image) => image.fileSize > maxFileSize);
|
||||
if (largeImage) {
|
||||
this.onShowFileSizeWarning(largeImage.fileName);
|
||||
const largeFile = files.find((image) => image.fileSize > maxFileSize);
|
||||
if (largeFile) {
|
||||
this.onShowFileSizeWarning(largeFile.fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleUploadFiles(images);
|
||||
this.handleUploadFiles(files);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -780,7 +780,7 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
onEndEditing={this.handleEndEditing}
|
||||
disableFullscreenUI={true}
|
||||
editable={!channelIsReadOnly}
|
||||
onPaste={this.handlePasteImages}
|
||||
onPaste={this.handlePasteFiles}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
/>
|
||||
<Fade visible={this.isSendButtonVisible()}>
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ export default class RadioButtonGroup extends PureComponent {
|
|||
return (
|
||||
<RadioButton
|
||||
{...other}
|
||||
ref={value}
|
||||
name={name}
|
||||
key={`${name}-${value}`}
|
||||
value={value}
|
||||
|
|
|
|||
|
|
@ -44,8 +44,12 @@ export default class Root extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setProviderRef = (ref) => {
|
||||
this.providerRef = ref;
|
||||
}
|
||||
|
||||
handleNoTeams = () => {
|
||||
if (!this.refs.provider) {
|
||||
if (!this.providerRef) {
|
||||
setTimeout(this.handleNoTeams, 200);
|
||||
return;
|
||||
}
|
||||
|
|
@ -53,7 +57,7 @@ export default class Root extends PureComponent {
|
|||
};
|
||||
|
||||
errorTeamsList = () => {
|
||||
if (!this.refs.provider) {
|
||||
if (!this.providerRef) {
|
||||
setTimeout(this.errorTeamsList, 200);
|
||||
return;
|
||||
}
|
||||
|
|
@ -62,7 +66,7 @@ export default class Root extends PureComponent {
|
|||
|
||||
navigateToTeamsPage = (screen) => {
|
||||
const {currentUrl, theme} = this.props;
|
||||
const {intl} = this.refs.provider.getChildContext();
|
||||
const {intl} = this.providerRef.getChildContext();
|
||||
|
||||
let passProps = {theme};
|
||||
const options = {topBar: {}};
|
||||
|
|
@ -99,7 +103,7 @@ export default class Root extends PureComponent {
|
|||
|
||||
return (
|
||||
<IntlProvider
|
||||
ref='provider'
|
||||
ref={this.setProviderRef}
|
||||
locale={locale}
|
||||
messages={getTranslations(locale)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3,15 +3,17 @@
|
|||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Keyboard, NativeModules, View} from 'react-native';
|
||||
import {Dimensions, Keyboard, NativeModules, View} from 'react-native';
|
||||
import SafeArea from 'react-native-safe-area';
|
||||
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import {DeviceTypes, ViewTypes} from 'app/constants';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
|
||||
const {StatusBarManager} = NativeModules;
|
||||
const {PORTRAIT, LANDSCAPE} = ViewTypes;
|
||||
|
||||
export default class SafeAreaIos extends PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -57,8 +59,12 @@ export default class SafeAreaIos extends PureComponent {
|
|||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
|
||||
SafeArea.addEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
Dimensions.addEventListener('change', this.getSafeAreaInsets);
|
||||
EventEmitter.on('update_safe_area_view', this.getSafeAreaInsets);
|
||||
if (EphemeralStore.safeAreaInsets[PORTRAIT] === null || EphemeralStore.safeAreaInsets[LANDSCAPE] === null) {
|
||||
SafeArea.addEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
}
|
||||
|
||||
this.keyboardDidShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
|
||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
|
||||
|
||||
|
|
@ -66,14 +72,40 @@ export default class SafeAreaIos extends PureComponent {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
Dimensions.removeEventListener('change', this.getSafeAreaInsets);
|
||||
EventEmitter.off('update_safe_area_view', this.getSafeAreaInsets);
|
||||
SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
this.keyboardDidShowListener.remove();
|
||||
this.keyboardDidHideListener.remove();
|
||||
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
getSafeAreaInsets = async (dimensions) => {
|
||||
this.getStatusBarHeight();
|
||||
|
||||
const safeAreaInsetsStored = EphemeralStore.safeAreaInsets[PORTRAIT] !== null && EphemeralStore.safeAreaInsets[LANDSCAPE] !== null;
|
||||
if ((DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets) && !safeAreaInsetsStored) {
|
||||
const window = dimensions?.window || Dimensions.get('window');
|
||||
const landscape = window.width > window.length;
|
||||
const {safeAreaInsets} = await SafeArea.getSafeAreaInsetsForRootView();
|
||||
this.setSafeAreaInsets(safeAreaInsets, landscape);
|
||||
}
|
||||
}
|
||||
|
||||
setSafeAreaInsets = (safeAreaInsets, landscape) => {
|
||||
const orientation = landscape ? LANDSCAPE : PORTRAIT;
|
||||
if (EphemeralStore.safeAreaInsets[orientation] === null) {
|
||||
EphemeralStore.safeAreaInsets[orientation] = safeAreaInsets;
|
||||
}
|
||||
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
safeAreaInsets: EphemeralStore.safeAreaInsets[orientation],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getStatusBarHeight = () => {
|
||||
try {
|
||||
StatusBarManager.getHeight(
|
||||
|
|
@ -88,26 +120,18 @@ export default class SafeAreaIos extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
getSafeAreaInsets = () => {
|
||||
this.getStatusBarHeight();
|
||||
onSafeAreaInsetsForRootViewChange = ({safeAreaInsets}) => {
|
||||
if (EphemeralStore.safeAreaInsets[PORTRAIT] !== null && EphemeralStore.safeAreaInsets[LANDSCAPE] !== null) {
|
||||
SafeArea.removeEventListener('safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsForRootViewChange);
|
||||
return;
|
||||
}
|
||||
|
||||
if (DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets) {
|
||||
SafeArea.getSafeAreaInsetsForRootView().then((result) => {
|
||||
const {safeAreaInsets} = result;
|
||||
|
||||
if (this.mounted) {
|
||||
this.setState({safeAreaInsets});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onSafeAreaInsetsForRootViewChange = (result) => {
|
||||
const {safeAreaInsets} = result;
|
||||
|
||||
if (this.mounted && (DeviceTypes.IS_IPHONE_WITH_INSETS || mattermostManaged.hasSafeAreaInsets)) {
|
||||
this.getStatusBarHeight();
|
||||
this.setState({safeAreaInsets});
|
||||
|
||||
const {width, height} = Dimensions.get('window');
|
||||
const landscape = width > height;
|
||||
this.setSafeAreaInsets(safeAreaInsets, landscape);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@ import {shallow} from 'enzyme';
|
|||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import {DeviceTypes, ViewTypes} from 'app/constants';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
|
||||
import SafeAreaIos from './safe_area_view.ios';
|
||||
|
||||
const {PORTRAIT, LANDSCAPE} = ViewTypes;
|
||||
|
||||
describe('SafeAreaIos', () => {
|
||||
const baseProps = {
|
||||
children: [],
|
||||
|
|
@ -37,10 +40,44 @@ describe('SafeAreaIos', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const PORTRAIT_INSETS = {
|
||||
safeAreaInsets: {
|
||||
top: 111,
|
||||
left: 111,
|
||||
bottom: 111,
|
||||
right: 111,
|
||||
},
|
||||
};
|
||||
|
||||
const LANDSCAPE_INSETS = {
|
||||
safeAreaInsets: {
|
||||
top: 222,
|
||||
left: 222,
|
||||
bottom: 222,
|
||||
right: 222,
|
||||
},
|
||||
};
|
||||
|
||||
const IGNORED_INSETS = {
|
||||
safeAreaInsets: {
|
||||
top: 333,
|
||||
left: 333,
|
||||
bottom: 333,
|
||||
right: 333,
|
||||
},
|
||||
};
|
||||
|
||||
SafeArea.getSafeAreaInsetsForRootView = jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve(TEST_INSETS_1);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
EphemeralStore.safeAreaInsets = {
|
||||
[PORTRAIT]: null,
|
||||
[LANDSCAPE]: null,
|
||||
};
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
|
|
@ -148,4 +185,144 @@ describe('SafeAreaIos', () => {
|
|||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_2);
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(TEST_INSETS_2.safeAreaInsets);
|
||||
});
|
||||
|
||||
test('should set portrait safe area insets', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
|
||||
const landscape = false;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(PORTRAIT_INSETS.safeAreaInsets, landscape);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should set portrait safe area insets from EphemeralStore', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = PORTRAIT_INSETS.safeAreaInsets;
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
|
||||
const landscape = false;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(IGNORED_INSETS.safeAreaInsets, landscape);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(PORTRAIT_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should set landscape safe area insets', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
|
||||
const landscape = true;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(LANDSCAPE_INSETS.safeAreaInsets, landscape);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should set landscape safe area insets from EphemeralStore', () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
|
||||
EphemeralStore.safeAreaInsets[LANDSCAPE] = LANDSCAPE_INSETS.safeAreaInsets;
|
||||
expect(wrapper.state().safeAreaInsets).not.toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
|
||||
const landscape = true;
|
||||
const instance = wrapper.instance();
|
||||
instance.setSafeAreaInsets(IGNORED_INSETS.safeAreaInsets, landscape);
|
||||
expect(wrapper.state().safeAreaInsets).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(LANDSCAPE_INSETS.safeAreaInsets);
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
});
|
||||
|
||||
test('should add safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are not set', () => {
|
||||
const addEventListener = jest.spyOn(SafeArea, 'addEventListener');
|
||||
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
let wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
let instance = wrapper.instance();
|
||||
expect(addEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange);
|
||||
addEventListener.mockClear();
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets;
|
||||
wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
expect(addEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange);
|
||||
addEventListener.mockClear();
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets;
|
||||
EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets;
|
||||
wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
expect(addEventListener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should remove safeAreaInsetsForRootViewDidChange listener when EphemeralStore values are set', () => {
|
||||
const removeEventListener = jest.spyOn(SafeArea, 'removeEventListener');
|
||||
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1);
|
||||
expect(removeEventListener).not.toHaveBeenCalled();
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets;
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1);
|
||||
expect(removeEventListener).not.toHaveBeenCalled();
|
||||
|
||||
EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets;
|
||||
instance.onSafeAreaInsetsForRootViewChange(TEST_INSETS_1);
|
||||
expect(removeEventListener).toHaveBeenCalledWith('safeAreaInsetsForRootViewDidChange', instance.onSafeAreaInsetsForRootViewChange);
|
||||
});
|
||||
|
||||
test('getSafeAreaInsets should set safe area insets when not already in ephemeral store', async () => {
|
||||
const wrapper = shallow(
|
||||
<SafeAreaIos {...baseProps}/>
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
const setSafeAreaInsets = jest.spyOn(instance, 'setSafeAreaInsets');
|
||||
|
||||
expect(EphemeralStore.safeAreaInsets[PORTRAIT]).toEqual(null);
|
||||
expect(EphemeralStore.safeAreaInsets[LANDSCAPE]).toEqual(null);
|
||||
await instance.getSafeAreaInsets();
|
||||
expect(setSafeAreaInsets).toHaveBeenCalled();
|
||||
setSafeAreaInsets.mockClear();
|
||||
|
||||
EphemeralStore.safeAreaInsets[PORTRAIT] = TEST_INSETS_1.safeAreaInsets; /* eslint-disable-line require-atomic-updates */
|
||||
await instance.getSafeAreaInsets();
|
||||
expect(setSafeAreaInsets).toHaveBeenCalled();
|
||||
setSafeAreaInsets.mockClear();
|
||||
|
||||
EphemeralStore.safeAreaInsets[LANDSCAPE] = TEST_INSETS_1.safeAreaInsets; /* eslint-disable-line require-atomic-updates */
|
||||
await instance.getSafeAreaInsets();
|
||||
expect(setSafeAreaInsets).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ export default class SearchBarAndroid extends PureComponent {
|
|||
};
|
||||
}
|
||||
|
||||
setInputRef = (ref) => {
|
||||
this.inputRef = ref;
|
||||
}
|
||||
|
||||
cancel = () => {
|
||||
this.onCancelButtonPress();
|
||||
};
|
||||
|
|
@ -123,11 +127,11 @@ export default class SearchBarAndroid extends PureComponent {
|
|||
};
|
||||
|
||||
blur = () => {
|
||||
this.refs.input.blur();
|
||||
this.inputRef.blur();
|
||||
};
|
||||
|
||||
focus = () => {
|
||||
this.refs.input.focus();
|
||||
this.inputRef.focus();
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
@ -207,7 +211,7 @@ export default class SearchBarAndroid extends PureComponent {
|
|||
/>
|
||||
}
|
||||
<TextInput
|
||||
ref='input'
|
||||
ref={this.setInputRef}
|
||||
blurOnSubmit={blurOnSubmit}
|
||||
refocusInput={this.state.refocusInput}
|
||||
value={this.props.value}
|
||||
|
|
|
|||
|
|
@ -56,8 +56,12 @@ export default class SearchBarIos extends PureComponent {
|
|||
searchIconExpandedMargin: 10,
|
||||
};
|
||||
|
||||
setSearchRef = (ref) => {
|
||||
this.searchRef = ref;
|
||||
}
|
||||
|
||||
cancel = () => {
|
||||
this.refs.search.onCancel();
|
||||
this.searchRef.onCancel();
|
||||
};
|
||||
|
||||
onBlur = () => {
|
||||
|
|
@ -94,18 +98,18 @@ export default class SearchBarIos extends PureComponent {
|
|||
};
|
||||
|
||||
blur = () => {
|
||||
this.refs.search.blur();
|
||||
this.searchRef.blur();
|
||||
};
|
||||
|
||||
focus = () => {
|
||||
this.refs.search.focus();
|
||||
this.searchRef.focus();
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Search
|
||||
{...this.props}
|
||||
ref='search'
|
||||
ref={this.setSearchRef}
|
||||
placeholderCollapsedMargin={33}
|
||||
placeholderExpandedMargin={33}
|
||||
shadowVisible={false}
|
||||
|
|
|
|||
|
|
@ -142,15 +142,23 @@ export default class Search extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
setSearchContainerRef = (ref) => {
|
||||
this.searchContainerRef = ref;
|
||||
}
|
||||
|
||||
setInputKeywordRef = (ref) => {
|
||||
this.inputKeywordRef = ref;
|
||||
}
|
||||
|
||||
blur = () => {
|
||||
this.refs.input_keyword.getNode().blur();
|
||||
this.inputKeywordRef.getNode().blur();
|
||||
this.setState({expanded: false});
|
||||
this.collapseAnimation();
|
||||
};
|
||||
|
||||
focus = () => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
const input = this.refs.input_keyword.getNode();
|
||||
const input = this.inputKeywordRef.getNode();
|
||||
if (!input.isFocused()) {
|
||||
input.focus();
|
||||
}
|
||||
|
|
@ -384,7 +392,7 @@ export default class Search extends Component {
|
|||
|
||||
return (
|
||||
<Animated.View
|
||||
ref='searchContainer'
|
||||
ref={this.setSearchContainerRef}
|
||||
style={[
|
||||
styles.container,
|
||||
this.props.backgroundColor && {backgroundColor: this.props.backgroundColor},
|
||||
|
|
@ -403,7 +411,7 @@ export default class Search extends Component {
|
|||
)}
|
||||
<Animated.View style={{backgroundColor, right: this.inputFocusAnimated, borderRadius: 2}}>
|
||||
<AnimatedTextInput
|
||||
ref='input_keyword'
|
||||
ref={this.setInputKeywordRef}
|
||||
style={[
|
||||
styles.input,
|
||||
this.props.placeholderTextColor && {color: this.props.placeholderTextColor},
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ exports[`ChannelItem should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -172,6 +173,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -287,6 +289,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -402,6 +405,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -506,6 +510,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -611,6 +616,7 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -720,6 +726,7 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -827,6 +834,7 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -894,7 +902,7 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
|
|||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"backgroundColor": "#ffffff",
|
||||
"height": 21,
|
||||
"padding": 3,
|
||||
"position": "relative",
|
||||
|
|
|
|||
|
|
@ -55,9 +55,13 @@ export default class ChannelsList extends PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
setSearchBarRef = (ref) => {
|
||||
this.searchBarRef = ref;
|
||||
}
|
||||
|
||||
cancelSearch = () => {
|
||||
if (this.refs.search_bar) {
|
||||
this.refs.search_bar.cancel();
|
||||
if (this.searchBarRef) {
|
||||
this.searchBarRef.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -135,7 +139,7 @@ export default class ChannelsList extends PureComponent {
|
|||
const title = (
|
||||
<View style={[styles.searchContainer, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref='search_bar'
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={intl.formatMessage({id: 'mobile.channel_drawer.search', defaultMessage: 'Jump to...'})}
|
||||
cancelTitle={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
inputCollapsedMargin={0}
|
||||
|
|
|
|||
|
|
@ -98,14 +98,18 @@ export default class List extends PureComponent {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.sections !== this.state.sections && this.refs.list._wrapperListRef.getListRef()._viewabilityHelper) { //eslint-disable-line
|
||||
this.refs.list.recordInteraction();
|
||||
if (prevState.sections !== this.state.sections && this.listRef?._wrapperListRef?.getListRef()._viewabilityHelper) { //eslint-disable-line
|
||||
this.listRef.recordInteraction();
|
||||
this.updateUnreadIndicators({
|
||||
viewableItems: Array.from(this.refs.list._wrapperListRef.getListRef()._viewabilityHelper._viewableItems.values()) //eslint-disable-line
|
||||
viewableItems: Array.from(this.listRef._wrapperListRef.getListRef()._viewabilityHelper._viewableItems.values()) //eslint-disable-line
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setListRef = (ref) => {
|
||||
this.listRef = ref;
|
||||
}
|
||||
|
||||
getSectionConfigByType = (props, sectionType) => {
|
||||
const {canCreatePrivateChannels, canJoinPublicChannels} = props;
|
||||
|
||||
|
|
@ -345,8 +349,8 @@ export default class List extends PureComponent {
|
|||
};
|
||||
|
||||
scrollToTop = () => {
|
||||
if (this.refs.list) {
|
||||
this.refs.list._wrapperListRef.getListRef().scrollToOffset({ //eslint-disable-line no-underscore-dangle
|
||||
if (this.listRef?._wrapperListRef) {
|
||||
this.listRef._wrapperListRef.getListRef().scrollToOffset({ //eslint-disable-line no-underscore-dangle
|
||||
x: 0,
|
||||
y: 0,
|
||||
animated: true,
|
||||
|
|
@ -402,7 +406,7 @@ export default class List extends PureComponent {
|
|||
onLayout={this.onLayout}
|
||||
>
|
||||
<SectionList
|
||||
ref='list'
|
||||
ref={this.setListRef}
|
||||
sections={sections}
|
||||
contentContainerStyle={{paddingBottom}}
|
||||
renderItem={this.renderItem}
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ export default class DrawerSwiper extends Component {
|
|||
}
|
||||
|
||||
runOnLayout = (shouldRun = true) => {
|
||||
if (this.refs.swiper) {
|
||||
this.refs.swiper.runOnLayout = shouldRun;
|
||||
if (this.swiperRef) {
|
||||
this.swiperRef.runOnLayout = shouldRun;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export default class ChannelSidebar extends Component {
|
|||
makeDirectChannel: PropTypes.func.isRequired,
|
||||
setChannelDisplayName: PropTypes.func.isRequired,
|
||||
setChannelLoading: PropTypes.func.isRequired,
|
||||
joinChannel: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
blurPostTextBox: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
|
|
@ -230,10 +231,11 @@ export default class ChannelSidebar extends Component {
|
|||
const {
|
||||
joinChannel,
|
||||
makeDirectChannel,
|
||||
setChannelLoading,
|
||||
} = actions;
|
||||
|
||||
this.closeChannelDrawer();
|
||||
actions.setChannelLoading(channel.id !== currentChannelId);
|
||||
setChannelLoading(channel.id !== currentChannelId);
|
||||
|
||||
setTimeout(async () => {
|
||||
const displayValue = {displayName: channel.display_name};
|
||||
|
|
@ -263,7 +265,7 @@ export default class ChannelSidebar extends Component {
|
|||
}
|
||||
|
||||
if (result.error || (!result.data && !result.data.channel)) {
|
||||
actions.setChannelLoading(false);
|
||||
setChannelLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ describe('MainSidebar', () => {
|
|||
makeDirectChannel: jest.fn(),
|
||||
setChannelDisplayName: jest.fn(),
|
||||
setChannelLoading: jest.fn(),
|
||||
joinChannel: jest.fn(),
|
||||
},
|
||||
blurPostTextBox: jest.fn(),
|
||||
currentTeamId: 'current-team-id',
|
||||
|
|
|
|||
|
|
@ -81,20 +81,32 @@ export default class SettingsDrawer extends PureComponent {
|
|||
Dimensions.removeEventListener('change', this.handleDimensions);
|
||||
}
|
||||
|
||||
setDrawerRef = (ref) => {
|
||||
this.drawerRef = ref;
|
||||
}
|
||||
|
||||
confirmReset = (status) => {
|
||||
const {intl} = this.context;
|
||||
confirmOutOfOfficeDisabled(intl, status, this.updateStatus);
|
||||
};
|
||||
|
||||
closeSettingsSidebar = () => {
|
||||
if (this.refs.drawer && this.drawerOpened) {
|
||||
this.refs.drawer.closeDrawer();
|
||||
if (this.drawerRef && this.drawerOpened) {
|
||||
this.drawerRef.closeDrawer();
|
||||
}
|
||||
};
|
||||
|
||||
openSettingsSidebar = () => {
|
||||
this.props.blurPostTextBox();
|
||||
|
||||
if (this.drawerRef && !this.drawerOpened) {
|
||||
this.drawerRef.openDrawer();
|
||||
}
|
||||
};
|
||||
|
||||
handleAndroidBack = () => {
|
||||
if (this.refs.drawer && this.drawerOpened) {
|
||||
this.refs.drawer.closeDrawer();
|
||||
if (this.drawerRef && this.drawerOpened) {
|
||||
this.drawerRef.closeDrawer();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -227,12 +239,25 @@ export default class SettingsDrawer extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
openSettingsSidebar = () => {
|
||||
this.props.blurPostTextBox();
|
||||
updateStatus = (status) => {
|
||||
const {currentUser: {id: currentUserId}} = this.props;
|
||||
this.props.actions.setStatus({
|
||||
user_id: currentUserId,
|
||||
status,
|
||||
});
|
||||
};
|
||||
|
||||
if (this.refs.drawer && !this.drawerOpened) {
|
||||
this.refs.drawer.openDrawer();
|
||||
setStatus = (status) => {
|
||||
const {status: currentUserStatus} = this.props;
|
||||
|
||||
if (currentUserStatus === General.OUT_OF_OFFICE) {
|
||||
dismissModal();
|
||||
this.closeSettingsSidebar();
|
||||
this.confirmReset(status);
|
||||
return;
|
||||
}
|
||||
this.updateStatus(status);
|
||||
EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL);
|
||||
};
|
||||
|
||||
renderUserStatusIcon = (userId) => {
|
||||
|
|
@ -341,27 +366,6 @@ export default class SettingsDrawer extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
updateStatus = (status) => {
|
||||
const {currentUser: {id: currentUserId}} = this.props;
|
||||
this.props.actions.setStatus({
|
||||
user_id: currentUserId,
|
||||
status,
|
||||
});
|
||||
};
|
||||
|
||||
setStatus = (status) => {
|
||||
const {status: currentUserStatus} = this.props;
|
||||
|
||||
if (currentUserStatus === General.OUT_OF_OFFICE) {
|
||||
dismissModal();
|
||||
this.closeSettingsSidebar();
|
||||
this.confirmReset(status);
|
||||
return;
|
||||
}
|
||||
this.updateStatus(status);
|
||||
EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {children} = this.props;
|
||||
const {deviceWidth, openDrawerOffset} = this.state;
|
||||
|
|
@ -369,7 +373,7 @@ export default class SettingsDrawer extends PureComponent {
|
|||
|
||||
return (
|
||||
<DrawerLayout
|
||||
ref='drawer'
|
||||
ref={this.setDrawerRef}
|
||||
renderNavigationView={this.renderNavigationView}
|
||||
onDrawerClose={this.handleDrawerClose}
|
||||
onDrawerOpen={this.handleDrawerOpen}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,16 @@ export default class TextInputWithLocalizedPlaceholder extends PureComponent {
|
|||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
setInputRef = (ref) => {
|
||||
this.inputRef = ref;
|
||||
}
|
||||
|
||||
blur = () => {
|
||||
this.refs.input.blur();
|
||||
this.inputRef.blur();
|
||||
};
|
||||
|
||||
focus = () => {
|
||||
this.refs.input.focus();
|
||||
this.inputRef.focus();
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
@ -40,7 +44,7 @@ export default class TextInputWithLocalizedPlaceholder extends PureComponent {
|
|||
|
||||
return (
|
||||
<TextInput
|
||||
ref='input'
|
||||
ref={this.setInputRef}
|
||||
{...otherProps}
|
||||
placeholder={placeholderString}
|
||||
disableFullscreenUI={true}
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import RNToolTip from 'react-native-tooltip';
|
||||
|
||||
import {setToolTipVisible, getToolTipVisible} from 'app/utils/tooltip';
|
||||
|
||||
export default class ToolTip extends PureComponent {
|
||||
static propTypes = {
|
||||
onHide: PropTypes.func,
|
||||
onShow: PropTypes.func,
|
||||
actions: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
actions: [],
|
||||
}
|
||||
|
||||
handleHide = () => {
|
||||
if (this.props.onHide) {
|
||||
this.props.onHide();
|
||||
}
|
||||
setToolTipVisible(false);
|
||||
};
|
||||
|
||||
handleShow = () => {
|
||||
if (this.props.onShow) {
|
||||
this.props.onShow();
|
||||
}
|
||||
setToolTipVisible();
|
||||
};
|
||||
|
||||
hideMenu = () => {
|
||||
if (this.refs.toolTip) {
|
||||
this.refs.toolTip.hideMenu();
|
||||
}
|
||||
};
|
||||
|
||||
showMenu = () => {
|
||||
if (this.refs.toolTip) {
|
||||
this.refs.toolTip.showMenu();
|
||||
}
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.actions.length !== this.props.actions.length && getToolTipVisible()) {
|
||||
this.refs.toolTip.hideMenu();
|
||||
setTimeout(() => this.refs.toolTip.showMenu(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<RNToolTip
|
||||
{...this.props}
|
||||
onHide={this.handleHide}
|
||||
onShow={this.handleShow}
|
||||
ref='toolTip'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -95,6 +95,9 @@ const ViewTypes = keyMirror({
|
|||
SELECTED_ACTION_MENU: null,
|
||||
SUBMIT_ATTACHMENT_MENU_ACTION: null,
|
||||
SELECT_CHANNEL_WITH_MEMBER: null,
|
||||
|
||||
PORTRAIT: null,
|
||||
LANDSCAPE: null,
|
||||
});
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ class EMMProvider {
|
|||
};
|
||||
|
||||
showNotSecuredAlert = (translations) => {
|
||||
return new Promise(async (resolve) => {
|
||||
return new Promise(async (resolve) => { /* eslint-disable-line no-async-promise-executor */
|
||||
const options = [];
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ Client4.doFetchWithResponse = async (url, options) => {
|
|||
if (headers[HEADER_X_CLUSTER_ID] || headers[HEADER_X_CLUSTER_ID.toLowerCase()]) {
|
||||
const clusterId = headers[HEADER_X_CLUSTER_ID] || headers[HEADER_X_CLUSTER_ID.toLowerCase()];
|
||||
if (clusterId && Client4.clusterId !== clusterId) {
|
||||
Client4.clusterId = clusterId;
|
||||
Client4.clusterId = clusterId; /* eslint-disable-line require-atomic-updates */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,14 @@ import semver from 'semver';
|
|||
|
||||
import {setAppState, setServerVersion} from 'mattermost-redux/actions/general';
|
||||
import {loadMe, logout} from 'mattermost-redux/actions/users';
|
||||
import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone';
|
||||
import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone';
|
||||
|
||||
import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusBarHeight} from 'app/actions/device';
|
||||
import {selectDefaultChannel} from 'app/actions/views/channel';
|
||||
|
|
@ -24,6 +27,7 @@ import PushNotifications from 'app/push_notifications';
|
|||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {deleteFileCache} from 'app/utils/file';
|
||||
import {getDeviceTimezoneAsync} from 'app/utils/timezone';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
|
|
@ -47,6 +51,7 @@ class GlobalEventHandler {
|
|||
|
||||
appActive = async () => {
|
||||
this.turnOnInAppNotificationHandling();
|
||||
this.setUserTimezone();
|
||||
|
||||
// if the app is being controlled by an EMM provider
|
||||
if (emmProvider.enabled && emmProvider.inAppPinCode) {
|
||||
|
|
@ -57,7 +62,7 @@ class GlobalEventHandler {
|
|||
await emmProvider.handleAuthentication(this.store, prompt);
|
||||
}
|
||||
|
||||
emmProvider.inBackgroundSince = null;
|
||||
emmProvider.inBackgroundSince = null; /* eslint-disable-line require-atomic-updates */
|
||||
};
|
||||
|
||||
appInactive = () => {
|
||||
|
|
@ -261,6 +266,18 @@ class GlobalEventHandler {
|
|||
showOverlay(screen, passProps);
|
||||
}
|
||||
};
|
||||
|
||||
setUserTimezone = async () => {
|
||||
const {dispatch, getState} = this.store;
|
||||
const state = getState();
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
|
||||
const enableTimezone = isTimezoneEnabled(state);
|
||||
if (enableTimezone && currentUserId) {
|
||||
const timezone = await getDeviceTimezoneAsync();
|
||||
dispatch(autoUpdateTimezone(timezone));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default new GlobalEventHandler();
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ jest.mock('react-native-notifications', () => ({
|
|||
NotificationCategory: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-native-status-bar-size', () => ({
|
||||
addEventListener: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
const store = mockStore(intitialState);
|
||||
GlobalEventHandler.store = store;
|
||||
|
|
@ -86,4 +90,14 @@ describe('GlobalEventHandler', () => {
|
|||
expect(appActive).not.toHaveBeenCalled();
|
||||
expect(appInactive).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set the user TimeZone when the app becomes active', () => {
|
||||
const onAppStateChange = jest.spyOn(GlobalEventHandler, 'onAppStateChange');
|
||||
const setUserTimezone = jest.spyOn(GlobalEventHandler, 'setUserTimezone');
|
||||
|
||||
GlobalEventHandler.configure({store});
|
||||
expect(GlobalEventHandler.store).not.toBeNull();
|
||||
expect(onAppStateChange).toHaveBeenCalledWith('active');
|
||||
expect(setUserTimezone).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ exports[`ChannelNavBar should match, full snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ exports[`ChannelDrawerButton should match, full snapshot 2`] = `
|
|||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": undefined,
|
||||
"backgroundColor": "#ffffff",
|
||||
"height": 19,
|
||||
"padding": 3,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
selectPost: PropTypes.func.isRequired,
|
||||
recordLoadTime: PropTypes.func.isRequired,
|
||||
refreshChannelWithRetry: PropTypes.func.isRequired,
|
||||
setChannelRefreshing: PropTypes.func,
|
||||
}).isRequired,
|
||||
channelId: PropTypes.string.isRequired,
|
||||
channelRefreshingFailed: PropTypes.bool,
|
||||
|
|
|
|||
|
|
@ -116,6 +116,10 @@ export default class ChannelAddMembers extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setSearchBarRef = (ref) => {
|
||||
this.searchBarRef = ref;
|
||||
}
|
||||
|
||||
clearSearch = () => {
|
||||
this.setState({term: '', searchResults: []});
|
||||
};
|
||||
|
|
@ -343,7 +347,7 @@ export default class ChannelAddMembers extends PureComponent {
|
|||
<StatusBar/>
|
||||
<View style={[style.searchBar, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref='search_bar'
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -95,6 +96,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -143,6 +145,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -191,6 +194,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -238,6 +242,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -287,6 +292,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -336,6 +342,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -385,6 +392,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -434,6 +442,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -484,6 +493,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -546,6 +556,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ exports[`channel_info_header should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -463,6 +464,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -907,6 +909,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -1323,6 +1326,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -1767,6 +1771,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -2205,6 +2210,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ export default class ChannelInfo extends PureComponent {
|
|||
currentChannelCreatorName: PropTypes.string,
|
||||
currentChannelMemberCount: PropTypes.number,
|
||||
currentChannelGuestCount: PropTypes.number,
|
||||
currentChannelPinnedPostCount: PropTypes.number,
|
||||
currentUserId: PropTypes.string,
|
||||
currentUserIsGuest: PropTypes.bool,
|
||||
status: PropTypes.string,
|
||||
|
|
@ -414,6 +415,7 @@ export default class ChannelInfo extends PureComponent {
|
|||
actionsRows = (style, channelIsArchived) => {
|
||||
const {
|
||||
currentChannelMemberCount,
|
||||
currentChannelPinnedPostCount,
|
||||
canManageUsers,
|
||||
canEditChannel,
|
||||
theme,
|
||||
|
|
@ -475,6 +477,7 @@ export default class ChannelInfo extends PureComponent {
|
|||
<ChannelInfoRow
|
||||
action={this.goToPinnedPosts}
|
||||
defaultMessage='Pinned Posts'
|
||||
detail={currentChannelPinnedPostCount}
|
||||
image={pinIcon}
|
||||
textId={t('channel_header.pinnedPosts')}
|
||||
theme={theme}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ function mapStateToProps(state) {
|
|||
const currentChannelCreatorName = currentChannelCreator && currentChannelCreator.username;
|
||||
const currentChannelStats = getCurrentChannelStats(state);
|
||||
const currentChannelMemberCount = currentChannelStats && currentChannelStats.member_count;
|
||||
const currentChannelPinnedPostCount = currentChannelStats && currentChannelStats.pinnedpost_count;
|
||||
let currentChannelGuestCount = (currentChannelStats && currentChannelStats.guest_count) || 0;
|
||||
const currentChannelMember = getMyCurrentChannelMembership(state);
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
|
|
@ -108,6 +109,7 @@ function mapStateToProps(state) {
|
|||
currentChannelCreatorName,
|
||||
currentChannelMemberCount,
|
||||
currentChannelGuestCount,
|
||||
currentChannelPinnedPostCount,
|
||||
currentUserId,
|
||||
currentUserIsGuest,
|
||||
isChannelMuted: isChannelMuted(currentChannelMember),
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ exports[`ChannelMembers should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -108,6 +108,10 @@ export default class ChannelMembers extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setSearchBarRef = (ref) => {
|
||||
this.searchBarRef = ref;
|
||||
}
|
||||
|
||||
clearSearch = () => {
|
||||
this.setState({term: '', searchResults: []});
|
||||
};
|
||||
|
|
@ -371,7 +375,7 @@ export default class ChannelMembers extends PureComponent {
|
|||
<StatusBar/>
|
||||
<View style={[style.searchBar, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref='search_bar'
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
|
|
|
|||
|
|
@ -13,17 +13,12 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import SyntaxHighlighter from 'react-native-syntax-highlighter';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {getCodeFont} from 'app/utils/markdown';
|
||||
|
||||
import {
|
||||
changeOpacity,
|
||||
makeStyleSheetFromTheme,
|
||||
setNavigatorStyles,
|
||||
getKeyboardAppearanceFromTheme,
|
||||
getHighlightStyleFromTheme,
|
||||
} from 'app/utils/theme';
|
||||
import {popTopScreen} from 'app/actions/navigation';
|
||||
|
||||
|
|
@ -32,8 +27,6 @@ export default class Code extends React.PureComponent {
|
|||
componentId: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
language: PropTypes.string,
|
||||
textStyle: CustomPropTypes.Style,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -80,20 +73,13 @@ export default class Code extends React.PureComponent {
|
|||
let textComponent;
|
||||
if (Platform.OS === 'ios') {
|
||||
textComponent = (
|
||||
<SyntaxHighlighter
|
||||
language={this.props.language}
|
||||
style={getHighlightStyleFromTheme(this.props.theme)}
|
||||
highlighter={'hljs'}
|
||||
CodeTag={TextInput}
|
||||
codeTagProps={{
|
||||
editable: false,
|
||||
multiline: true,
|
||||
keyboardAppearance: getKeyboardAppearanceFromTheme(this.props.theme),
|
||||
style: {...style.codeText, ...this.props.textStyle},
|
||||
}}
|
||||
>
|
||||
{this.props.content}
|
||||
</SyntaxHighlighter>
|
||||
<TextInput
|
||||
editable={false}
|
||||
multiline={true}
|
||||
value={this.props.content}
|
||||
style={[style.codeText]}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(this.props.theme)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
textComponent = (
|
||||
|
|
@ -159,7 +145,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
flexGrow: 0,
|
||||
flexShrink: 1,
|
||||
width: '100%',
|
||||
backgroundColor: getHighlightStyleFromTheme(theme).hljs.background,
|
||||
},
|
||||
code: {
|
||||
paddingHorizontal: 6,
|
||||
|
|
@ -167,6 +152,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
android: {
|
||||
paddingVertical: 4,
|
||||
},
|
||||
ios: {
|
||||
top: -4,
|
||||
},
|
||||
}),
|
||||
},
|
||||
codeText: {
|
||||
|
|
@ -176,7 +164,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
lineHeight: 18,
|
||||
...Platform.select({
|
||||
ios: {
|
||||
top: -11,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ import {
|
|||
View,
|
||||
} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
|
||||
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
|
||||
import ErrorText from 'app/components/error_text';
|
||||
import Loading from 'app/components/loading';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
|
|
@ -56,7 +58,11 @@ export default class EditPost extends PureComponent {
|
|||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this.state = {message: props.post.message};
|
||||
this.state = {
|
||||
message: props.post.message,
|
||||
cursorPosition: 0,
|
||||
};
|
||||
|
||||
this.rightButton.color = props.theme.sidebarHeaderTextColor;
|
||||
this.rightButton.text = context.intl.formatMessage({id: 'edit_post.save', defaultMessage: 'Save'});
|
||||
|
||||
|
|
@ -152,6 +158,11 @@ export default class EditPost extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
onPostSelectionChange = (event) => {
|
||||
const cursorPosition = event.nativeEvent.selection.end;
|
||||
this.setState({cursorPosition});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {deviceHeight, deviceWidth, theme, isLandscape} = this.props;
|
||||
const {editing, message, error} = this.state;
|
||||
|
|
@ -200,9 +211,19 @@ export default class EditPost extends PureComponent {
|
|||
underlineColorAndroid='transparent'
|
||||
disableFullscreenUI={true}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(this.props.theme)}
|
||||
onSelectionChange={this.onPostSelectionChange}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<KeyboardTrackingView style={style.autocompleteContainer}>
|
||||
<Autocomplete
|
||||
cursorPosition={this.state.cursorPosition}
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.onPostChangeText}
|
||||
value={message}
|
||||
nestedScrollEnabled={true}
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -238,5 +259,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
padding: 15,
|
||||
textAlignVertical: 'top',
|
||||
},
|
||||
autocompleteContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ exports[`edit_profile should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {intlShape} from 'react-intl';
|
|||
import {Alert, View} from 'react-native';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
||||
import {DocumentPickerUtil} from 'react-native-document-picker';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
|
|
@ -521,7 +521,7 @@ export default class EditProfile extends PureComponent {
|
|||
currentUser={currentUser}
|
||||
theme={theme}
|
||||
blurTextBox={emptyFunction}
|
||||
browseFileTypes={DocumentPickerUtil.images()}
|
||||
browseFileTypes={DocumentPicker.types.images}
|
||||
canTakeVideo={false}
|
||||
canBrowseVideoLibrary={false}
|
||||
maxFileSize={MAX_SIZE}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ exports[`ErrorTeamsList should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ exports[`FlaggedPosts should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@ export default class FlaggedPosts extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setListRef = (ref) => {
|
||||
this.listRef = ref;
|
||||
}
|
||||
|
||||
goToThread = (post) => {
|
||||
const {actions} = this.props;
|
||||
const channelId = post.channel_id;
|
||||
|
|
@ -212,7 +216,7 @@ export default class FlaggedPosts extends PureComponent {
|
|||
} else if (postIds.length) {
|
||||
component = (
|
||||
<FlatList
|
||||
ref='list'
|
||||
ref={this.setListRef}
|
||||
contentContainerStyle={style.sectionList}
|
||||
data={postIds}
|
||||
keyExtractor={this.keyExtractor}
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ export default class Downloader extends PureComponent {
|
|||
let path = res.path();
|
||||
|
||||
if (saveToCameraRoll) {
|
||||
path = await CameraRoll.saveToCameraRoll(path, 'photo');
|
||||
path = await CameraRoll.saveToCameraRoll(path, 'photo'); /* eslint-disable-line require-atomic-updates */
|
||||
}
|
||||
|
||||
if (this.mounted) {
|
||||
|
|
|
|||
|
|
@ -99,6 +99,14 @@ export default class ImagePreview extends PureComponent {
|
|||
StatusBar.setHidden(false, 'fade');
|
||||
}
|
||||
|
||||
setDocumentRef = (ref) => {
|
||||
this.documents[this.state.index] = ref;
|
||||
}
|
||||
|
||||
setDownloaderRef = (ref) => {
|
||||
this.downloaderRef = ref;
|
||||
}
|
||||
|
||||
animateOpenAnimToValue = (toValue, onComplete) => {
|
||||
Animated.timing(this.openAnim, {
|
||||
...ANIM_CONFIG,
|
||||
|
|
@ -194,9 +202,7 @@ export default class ImagePreview extends PureComponent {
|
|||
return (
|
||||
<View style={[style.flex, style.center]}>
|
||||
<FileAttachmentDocument
|
||||
ref={(ref) => {
|
||||
this.documents[this.state.index] = ref;
|
||||
}}
|
||||
ref={this.setDocumentRef}
|
||||
backgroundColor='transparent'
|
||||
canDownloadFiles={canDownloadFiles}
|
||||
file={file}
|
||||
|
|
@ -278,7 +284,7 @@ export default class ImagePreview extends PureComponent {
|
|||
|
||||
return (
|
||||
<Downloader
|
||||
ref='downloader'
|
||||
ref={this.setDownloaderRef}
|
||||
show={this.state.showDownloader}
|
||||
file={file}
|
||||
deviceHeight={deviceHeight}
|
||||
|
|
@ -414,9 +420,9 @@ export default class ImagePreview extends PureComponent {
|
|||
saveVideoIOS = () => {
|
||||
const file = this.getCurrentFile();
|
||||
|
||||
if (this.refs.downloader) {
|
||||
if (this.downloaderRef) {
|
||||
EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL);
|
||||
this.refs.downloader.saveVideo(getLocalFilePathFromFile(VIDEOS_PATH, file));
|
||||
this.downloaderRef.saveVideo(getLocalFilePathFromFile(VIDEOS_PATH, file));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,14 @@ export default class VideoPreview extends PureComponent {
|
|||
EventEmitter.off('stop-video-playback', this.stopPlayback);
|
||||
}
|
||||
|
||||
setVideoPlayerRef = (ref) => {
|
||||
this.videoPlayerRef = ref;
|
||||
}
|
||||
|
||||
setControlsRef = (ref) => {
|
||||
this.controlsRef = ref;
|
||||
}
|
||||
|
||||
async initializeComponent() {
|
||||
const {file} = this.props;
|
||||
const prefix = Platform.OS === 'android' ? 'file:/' : '';
|
||||
|
|
@ -210,7 +218,7 @@ export default class VideoPreview extends PureComponent {
|
|||
onPress={() => this.refs.controls.fadeInControls()}
|
||||
>
|
||||
<Video
|
||||
ref='videoPlayer'
|
||||
ref={this.setVideoPlayerRef}
|
||||
style={[StyleSheet.absoluteFill, {position: 'absolute'}]}
|
||||
resizeMode='contain'
|
||||
source={{uri: path}}
|
||||
|
|
@ -224,7 +232,7 @@ export default class VideoPreview extends PureComponent {
|
|||
/>
|
||||
</TouchableOpacity>
|
||||
<VideoControls
|
||||
ref='controls'
|
||||
ref={this.setControlsRef}
|
||||
mainColor={theme.linkColor}
|
||||
playerState={playerState}
|
||||
isFullScreen={isFullScreen}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ exports[`InteractiveDialog should display introduction text if present 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
349
app/screens/long_post/__snapshots__/long_post.test.js.snap
Normal file
349
app/screens/long_post/__snapshots__/long_post.test.js.snap
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LongPost should match snapshot 1`] = `
|
||||
LongPost {
|
||||
"context": Object {
|
||||
"intl": Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": null,
|
||||
},
|
||||
},
|
||||
"goToThread": [Function],
|
||||
"handleClose": [Function],
|
||||
"handlePress": [Function],
|
||||
"navigationEventListener": undefined,
|
||||
"props": Object {
|
||||
"actions": Object {
|
||||
"loadThreadIfNecessary": [MockFunction],
|
||||
"selectPost": [MockFunction],
|
||||
},
|
||||
"fileIds": Array [],
|
||||
"intl": Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": null,
|
||||
},
|
||||
"isLandscape": false,
|
||||
"postId": "post-id",
|
||||
"theme": Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
},
|
||||
},
|
||||
"refs": Object {},
|
||||
"setState": [Function],
|
||||
"setViewRef": [Function],
|
||||
"state": null,
|
||||
"updater": Updater {
|
||||
"_callbacks": Array [],
|
||||
"_renderer": ReactShallowRenderer {
|
||||
"_context": Object {
|
||||
"intl": Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": null,
|
||||
},
|
||||
},
|
||||
"_didScheduleRenderPhaseUpdate": false,
|
||||
"_dispatcher": Object {
|
||||
"readContext": [Function],
|
||||
"useCallback": [Function],
|
||||
"useContext": [Function],
|
||||
"useDebugValue": [Function],
|
||||
"useEffect": [Function],
|
||||
"useImperativeHandle": [Function],
|
||||
"useLayoutEffect": [Function],
|
||||
"useMemo": [Function],
|
||||
"useReducer": [Function],
|
||||
"useRef": [Function],
|
||||
"useResponder": [Function],
|
||||
"useState": [Function],
|
||||
},
|
||||
"_element": <LongPost
|
||||
actions={
|
||||
Object {
|
||||
"loadThreadIfNecessary": [MockFunction],
|
||||
"selectPost": [MockFunction],
|
||||
}
|
||||
}
|
||||
fileIds={Array []}
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": "span",
|
||||
"timeZone": null,
|
||||
}
|
||||
}
|
||||
isLandscape={false}
|
||||
postId="post-id"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
"buttonBg": "#166de0",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3d3c40",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
"mentionHighlightLink": "#166de0",
|
||||
"newMessageSeparator": "#ff8800",
|
||||
"onlineIndicator": "#06d6a0",
|
||||
"sidebarBg": "#145dbf",
|
||||
"sidebarHeaderBg": "#1153ab",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#579eff",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#4578bf",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
/>,
|
||||
"_firstWorkInProgressHook": null,
|
||||
"_forcedUpdate": false,
|
||||
"_instance": [Circular],
|
||||
"_isReRender": false,
|
||||
"_newState": null,
|
||||
"_numberOfReRenders": 0,
|
||||
"_renderPhaseUpdates": null,
|
||||
"_rendered": <Connect(SafeAreaIos)
|
||||
backgroundColor="transparent"
|
||||
excludeHeader={true}
|
||||
footerColor="transparent"
|
||||
forceTop={44}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"marginTop": 20,
|
||||
},
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
<withAnimatable(View)
|
||||
animation="zoomIn"
|
||||
delay={0}
|
||||
direction="normal"
|
||||
duration={200}
|
||||
iterationCount={1}
|
||||
iterationDelay={0}
|
||||
onAnimationBegin={[Function]}
|
||||
onAnimationEnd={[Function]}
|
||||
onTransitionBegin={[Function]}
|
||||
onTransitionEnd={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderRadius": 6,
|
||||
"flex": 1,
|
||||
"margin": 10,
|
||||
"opacity": 0,
|
||||
}
|
||||
}
|
||||
useNativeDriver={true}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderBottomColor": "rgba(61,60,64,0.2)",
|
||||
"borderBottomWidth": 1,
|
||||
"borderTopLeftRadius": 6,
|
||||
"borderTopRightRadius": 6,
|
||||
"flexDirection": "row",
|
||||
"height": 44,
|
||||
"paddingRight": 16,
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.2}
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": 44,
|
||||
"justifyContent": "center",
|
||||
"paddingLeft": 7,
|
||||
"width": 40,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
color="#3d3c40"
|
||||
name="close"
|
||||
size={20}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"paddingRight": 40,
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedText
|
||||
defaultMessage="{channelName} - Post"
|
||||
ellipsizeMode="tail"
|
||||
id="mobile.long_post_title"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "600",
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
"channelName": undefined,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<ScrollViewMock
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(Post)
|
||||
isSearchResult={false}
|
||||
onPress={[Function]}
|
||||
postId="post-id"
|
||||
shouldRenderReplyButton={false}
|
||||
showAddReaction={false}
|
||||
showLongPost={true}
|
||||
/>
|
||||
</ScrollViewMock>
|
||||
</withAnimatable(View)>
|
||||
</View>
|
||||
</Connect(SafeAreaIos)>,
|
||||
"_rendering": false,
|
||||
"_updater": [Circular],
|
||||
"_workInProgressHook": null,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
|
@ -6,7 +6,7 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {selectPost} from 'mattermost-redux/actions/posts';
|
||||
import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getPost, makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getPost} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {loadThreadIfNecessary} from 'app/actions/views/channel';
|
||||
|
|
@ -15,16 +15,13 @@ import LongPost from './long_post';
|
|||
|
||||
function makeMapStateToProps() {
|
||||
const getChannel = makeGetChannel();
|
||||
const getReactionsForPost = makeGetReactionsForPost();
|
||||
|
||||
return function mapStateToProps(state, ownProps) {
|
||||
const post = getPost(state, ownProps.postId);
|
||||
const channel = post ? getChannel(state, {id: post.channel_id}) : null;
|
||||
const reactions = getReactionsForPost(state, post.id);
|
||||
|
||||
return {
|
||||
channelName: channel ? channel.display_name : '',
|
||||
hasReactions: (reactions && Object.keys(reactions).length > 0) || Boolean(post.has_reactions),
|
||||
inThreadView: Boolean(state.entities.posts.selectedPostId),
|
||||
fileIds: post ? post.file_ids : false,
|
||||
theme: getTheme(state),
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import {Navigation} from 'react-native-navigation';
|
|||
import FileAttachmentList from 'app/components/file_attachment_list';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Post from 'app/components/post';
|
||||
import Reactions from 'app/components/reactions';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import {marginHorizontal as margin} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
|
|
@ -49,7 +48,6 @@ export default class LongPost extends PureComponent {
|
|||
}).isRequired,
|
||||
channelName: PropTypes.string,
|
||||
fileIds: PropTypes.array,
|
||||
hasReactions: PropTypes.bool,
|
||||
isPermalink: PropTypes.bool,
|
||||
inThreadView: PropTypes.bool,
|
||||
managedConfig: PropTypes.object,
|
||||
|
|
@ -78,6 +76,10 @@ export default class LongPost extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setViewRef = (ref) => {
|
||||
this.viewRef = ref;
|
||||
}
|
||||
|
||||
goToThread = preventDoubleTap((post) => {
|
||||
const {actions} = this.props;
|
||||
const channelId = post.channel_id;
|
||||
|
|
@ -96,8 +98,8 @@ export default class LongPost extends PureComponent {
|
|||
});
|
||||
|
||||
handleClose = () => {
|
||||
if (this.refs.view) {
|
||||
this.refs.view.zoomOut().then(() => {
|
||||
if (this.viewRef) {
|
||||
this.viewRef.zoomOut().then(() => {
|
||||
dismissModal();
|
||||
});
|
||||
}
|
||||
|
|
@ -136,28 +138,10 @@ export default class LongPost extends PureComponent {
|
|||
return attachments;
|
||||
}
|
||||
|
||||
renderReactions = (style) => {
|
||||
const {hasReactions, postId} = this.props;
|
||||
|
||||
if (!hasReactions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.reactions}>
|
||||
<Reactions
|
||||
position='left'
|
||||
postId={postId}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
channelName,
|
||||
fileIds,
|
||||
hasReactions,
|
||||
managedConfig,
|
||||
onHashtagPress,
|
||||
onPermalinkPress,
|
||||
|
|
@ -168,11 +152,10 @@ export default class LongPost extends PureComponent {
|
|||
const style = getStyleSheet(theme);
|
||||
|
||||
let footer;
|
||||
if (hasReactions || fileIds.length) {
|
||||
if (fileIds.length) {
|
||||
footer = (
|
||||
<View style={style.footer}>
|
||||
{this.renderFileAttachments(style)}
|
||||
{this.renderReactions(style)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -186,7 +169,7 @@ export default class LongPost extends PureComponent {
|
|||
>
|
||||
<View style={[style.container, margin(isLandscape)]}>
|
||||
<Animatable.View
|
||||
ref='view'
|
||||
ref={this.setViewRef}
|
||||
animation='zoomIn'
|
||||
duration={200}
|
||||
delay={0}
|
||||
|
|
@ -222,6 +205,7 @@ export default class LongPost extends PureComponent {
|
|||
onPress={this.handlePress}
|
||||
isSearchResult={false}
|
||||
showLongPost={true}
|
||||
showAddReaction={false}
|
||||
onHashtagPress={onHashtagPress}
|
||||
onPermalinkPress={onPermalinkPress}
|
||||
managedConfig={managedConfig}
|
||||
|
|
@ -297,9 +281,5 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
height: 95,
|
||||
width: '100%',
|
||||
},
|
||||
reactions: {
|
||||
height: 47,
|
||||
width: '100%',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
32
app/screens/long_post/long_post.test.js
Normal file
32
app/screens/long_post/long_post.test.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import LongPost from './long_post';
|
||||
|
||||
jest.mock('react-native-doc-viewer', () => ({
|
||||
openDoc: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('LongPost', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
loadThreadIfNecessary: jest.fn(),
|
||||
selectPost: jest.fn(),
|
||||
},
|
||||
postId: 'post-id',
|
||||
theme: Preferences.THEMES.default,
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallowWithIntl(<LongPost {...baseProps}/>);
|
||||
|
||||
expect(wrapper.instance()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -96,6 +96,7 @@ exports[`MoreChannels should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -125,6 +125,10 @@ export default class MoreChannels extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setSearchBarRef = (ref) => {
|
||||
this.searchBarRef = ref;
|
||||
}
|
||||
|
||||
cancelSearch = () => {
|
||||
const {channels} = this.props;
|
||||
|
||||
|
|
@ -340,7 +344,7 @@ export default class MoreChannels extends PureComponent {
|
|||
<React.Fragment>
|
||||
<View style={[style.searchBar, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref='search_bar'
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
|
|
|
|||
|
|
@ -113,6 +113,10 @@ export default class MoreDirectMessages extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setSearchBarRef = (ref) => {
|
||||
this.searchBarRef = ref;
|
||||
}
|
||||
|
||||
close = () => {
|
||||
dismissModal();
|
||||
};
|
||||
|
|
@ -460,7 +464,7 @@ export default class MoreDirectMessages extends PureComponent {
|
|||
<StatusBar/>
|
||||
<View style={[style.searchBar, padding(isLandscape)]}>
|
||||
<SearchBar
|
||||
ref='search_bar'
|
||||
ref={this.setSearchBarRef}
|
||||
placeholder={formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelTitle={formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ exports[`SelectedUsers should match snapshot 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -109,6 +110,7 @@ exports[`SelectedUsers should match snapshot for no warning message 1`] = `
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -168,6 +170,7 @@ exports[`SelectedUsers should match snapshot to show warning for ability to add
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
@ -205,6 +208,7 @@ exports[`SelectedUsers should match snapshot to show warning for ability to add
|
|||
"dndIndicator": "#f74343",
|
||||
"errorTextColor": "#fd5960",
|
||||
"linkColor": "#2389d7",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionBj": "#ffffff",
|
||||
"mentionColor": "#145dbf",
|
||||
"mentionHighlightBg": "#ffe577",
|
||||
|
|
|
|||
|
|
@ -178,6 +178,10 @@ export default class Permalink extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setViewRef = (ref) => {
|
||||
this.viewRef = ref;
|
||||
}
|
||||
|
||||
goToThread = preventDoubleTap((post) => {
|
||||
const {actions} = this.props;
|
||||
const channelId = post.channel_id;
|
||||
|
|
@ -197,9 +201,9 @@ export default class Permalink extends PureComponent {
|
|||
|
||||
handleClose = () => {
|
||||
const {actions, onClose} = this.props;
|
||||
if (this.refs.view) {
|
||||
if (this.viewRef) {
|
||||
this.mounted = false;
|
||||
this.refs.view.zoomOut().then(() => {
|
||||
this.viewRef.zoomOut().then(() => {
|
||||
actions.selectPost('');
|
||||
dismissModal();
|
||||
|
||||
|
|
@ -221,8 +225,8 @@ export default class Permalink extends PureComponent {
|
|||
handlePress = () => {
|
||||
const {channelIdState} = this.state;
|
||||
|
||||
if (this.refs.view) {
|
||||
this.refs.view.growOut().then(() => {
|
||||
if (this.viewRef) {
|
||||
this.viewRef.growOut().then(() => {
|
||||
this.jumpToChannel(channelIdState);
|
||||
});
|
||||
}
|
||||
|
|
@ -398,7 +402,7 @@ export default class Permalink extends PureComponent {
|
|||
style={[style.container, margin(isLandscape)]}
|
||||
>
|
||||
<Animatable.View
|
||||
ref='view'
|
||||
ref={this.setViewRef}
|
||||
animation='zoomIn'
|
||||
duration={200}
|
||||
delay={0}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ export default class PinnedPosts extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
setListRef = (ref) => {
|
||||
this.listRef = ref;
|
||||
}
|
||||
|
||||
goToThread = (post) => {
|
||||
const {actions} = this.props;
|
||||
const channelId = post.channel_id;
|
||||
|
|
@ -209,7 +213,7 @@ export default class PinnedPosts extends PureComponent {
|
|||
} else if (postIds.length) {
|
||||
component = (
|
||||
<FlatList
|
||||
ref='list'
|
||||
ref={this.setListRef}
|
||||
contentContainerStyle={style.sectionList}
|
||||
data={postIds}
|
||||
keyExtractor={this.keyExtractor}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue