forked from Ivasoft/mattermost-mobile
Compare commits
48 Commits
release-1.
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aebff0f2af | ||
|
|
6384f38ea3 | ||
|
|
a9497c3e1d | ||
|
|
8caa48f1f7 | ||
|
|
0af61964e8 | ||
|
|
ebe57e9950 | ||
|
|
a0eb5192ce | ||
|
|
46a4c3ebef | ||
|
|
a239773ffb | ||
|
|
94d5501d05 | ||
|
|
dd30a159d1 | ||
|
|
f014c8557f | ||
|
|
c08d97f67c | ||
|
|
40a0e40225 | ||
|
|
d19e56a457 | ||
|
|
0569479398 | ||
|
|
8dac3ce9b9 | ||
|
|
e5ca1c17db | ||
|
|
b5431a293f | ||
|
|
ba1ba47265 | ||
|
|
5979cf6b43 | ||
|
|
3316c4a5ec | ||
|
|
0b21e31f01 | ||
|
|
abab4855d2 | ||
|
|
f23190cf29 | ||
|
|
768e2f1b84 | ||
|
|
a0758fd0cd | ||
|
|
d5a408b967 | ||
|
|
95599b2c8a | ||
|
|
349726641c | ||
|
|
4602fb916f | ||
|
|
c9ce2338f8 | ||
|
|
a3dae17ded | ||
|
|
c815325034 | ||
|
|
33cfb52bab | ||
|
|
eeb26eac35 | ||
|
|
e711b36789 | ||
|
|
e9791b7bee | ||
|
|
d8ff0e52bb | ||
|
|
5778019074 | ||
|
|
d67b7a2deb | ||
|
|
dbefec6f17 | ||
|
|
12fe0465c2 | ||
|
|
aa22d11f79 | ||
|
|
8cfa485f51 | ||
|
|
ae93498d50 | ||
|
|
60385eeae8 | ||
|
|
22af5690ac |
@@ -13,7 +13,7 @@ executors:
|
||||
NODE_ENV: production
|
||||
BABEL_ENV: production
|
||||
docker:
|
||||
- image: circleci/android:api-27-node
|
||||
- image: circleci/android:api-29-node
|
||||
working_directory: ~/mattermost-mobile
|
||||
resource_class: <<parameters.resource_class>>
|
||||
|
||||
@@ -23,7 +23,7 @@ executors:
|
||||
NODE_ENV: production
|
||||
BABEL_ENV: production
|
||||
macos:
|
||||
xcode: "11.0.0"
|
||||
xcode: "12.0.0"
|
||||
working_directory: ~/mattermost-mobile
|
||||
shell: /bin/bash --login -o pipefail
|
||||
|
||||
@@ -44,7 +44,6 @@ commands:
|
||||
for:
|
||||
type: string
|
||||
steps:
|
||||
- ruby-setup
|
||||
- restore_cache:
|
||||
name: Restore Fastlane cache
|
||||
key: v1-gems-<< parameters.for >>-{{ checksum "fastlane/Gemfile.lock" }}-{{ arch }}
|
||||
@@ -82,7 +81,7 @@ commands:
|
||||
key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }}
|
||||
- run:
|
||||
name: Generate assets
|
||||
command: make dist/assets
|
||||
command: node ./scripts/generate-assets.js
|
||||
- save_cache:
|
||||
name: Save assets cache
|
||||
key: v1-assets-{{ checksum "assets/base/config.json" }}-{{ arch }}
|
||||
@@ -104,8 +103,8 @@ commands:
|
||||
paths:
|
||||
- node_modules
|
||||
- run:
|
||||
name: "Run post install scripts"
|
||||
command: make post-install
|
||||
name: "Patch dependencies"
|
||||
command: npx patch-package
|
||||
|
||||
pods-dependencies:
|
||||
description: "Get cocoapods dependencies"
|
||||
@@ -113,10 +112,12 @@ commands:
|
||||
- restore_cache:
|
||||
name: Restore cocoapods specs and pods
|
||||
key: v1-cocoapods-{{ checksum "ios/Podfile.lock" }}-{{ arch }}
|
||||
- run:
|
||||
name: iOS gems
|
||||
command: npm run ios-gems
|
||||
- run:
|
||||
name: Getting cocoapods dependencies
|
||||
working_directory: ios
|
||||
command: pod install
|
||||
command: npm run pod-install
|
||||
- save_cache:
|
||||
name: Save cocoapods specs and pods cache
|
||||
key: v1-cocoapods-{{ checksum "ios/Podfile.lock" }}-{{ arch }}
|
||||
@@ -143,11 +144,14 @@ commands:
|
||||
echo MATTERMOST_RELEASE_STORE_FILE=${STORE_FILE} | tee -a android/gradle.properties > /dev/null
|
||||
echo ${STORE_ALIAS} | tee -a android/gradle.properties > /dev/null
|
||||
echo ${STORE_PASSWORD} | tee -a android/gradle.properties > /dev/null
|
||||
- run:
|
||||
name: Jetify android libraries
|
||||
command: ./node_modules/.bin/jetify
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build android
|
||||
no_output_timeout: 30m
|
||||
command: bundle exec fastlane android build
|
||||
command: export TERM=xterm && bundle exec fastlane android build
|
||||
|
||||
build-ios:
|
||||
description: "Build the iOS app"
|
||||
@@ -165,7 +169,7 @@ commands:
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install watchman
|
||||
bundle exec fastlane ios build
|
||||
export TERM=xterm && bundle exec fastlane ios build
|
||||
|
||||
deploy-to-store:
|
||||
description: "Deploy build to store"
|
||||
@@ -197,15 +201,14 @@ commands:
|
||||
parameters:
|
||||
filename:
|
||||
type: string
|
||||
steps:
|
||||
- store_artifacts:
|
||||
path: ~/mattermost-mobile/<<parameters.filename>>
|
||||
|
||||
ruby-setup:
|
||||
steps:
|
||||
- run:
|
||||
name: Set Ruby Version
|
||||
command: echo "ruby-2.6.3" > ~/.ruby-version
|
||||
name: Copying artifacts
|
||||
command: |
|
||||
mkdir /tmp/artifacts;
|
||||
cp ~/mattermost-mobile/<<parameters.filename>> /tmp/artifacts;
|
||||
- store_artifacts:
|
||||
path: /tmp/artifacts
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -225,19 +228,7 @@ jobs:
|
||||
command: npm test
|
||||
- run:
|
||||
name: Check i18n
|
||||
command: |
|
||||
mkdir -p tmp
|
||||
cp assets/base/i18n/en.json tmp/en.json
|
||||
mkdir -p tmp/fake-webapp-dir/i18n/
|
||||
echo '{}' > tmp/fake-webapp-dir/i18n/en.json
|
||||
|
||||
npm run mmjstool -- i18n extract-mobile --webapp-dir tmp/fake-webapp-dir --mobile-dir .
|
||||
diff tmp/en.json assets/base/i18n/en.json
|
||||
# Address weblate behavior which does not remove whole translation item when translation string is set to empty
|
||||
npm run mmjstool -- i18n clean-empty --webapp-dir tmp/fake-webapp-dir --mobile-dir . --check
|
||||
|
||||
rm -rf tmp
|
||||
|
||||
command: ./scripts/precommit/i18n.sh
|
||||
|
||||
check-deps:
|
||||
parameters:
|
||||
@@ -297,7 +288,7 @@ jobs:
|
||||
- build-android
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost_Beta.apk"
|
||||
filename: "*.apk"
|
||||
|
||||
build-android-release:
|
||||
executor: android
|
||||
@@ -305,7 +296,7 @@ jobs:
|
||||
- build-android
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost.apk"
|
||||
filename: "*.apk"
|
||||
|
||||
build-android-pr:
|
||||
executor: android
|
||||
@@ -314,7 +305,7 @@ jobs:
|
||||
steps:
|
||||
- build-android
|
||||
- save:
|
||||
filename: "Mattermost_Beta.apk"
|
||||
filename: "*.apk"
|
||||
|
||||
build-android-unsigned:
|
||||
executor: android
|
||||
@@ -326,6 +317,9 @@ jobs:
|
||||
- fastlane-dependencies:
|
||||
for: android
|
||||
- gradle-dependencies
|
||||
- run:
|
||||
name: Jetify Android libraries
|
||||
command: ./node_modules/.bin/jetify
|
||||
- run:
|
||||
working_directory: fastlane
|
||||
name: Run fastlane to build unsigned android
|
||||
@@ -333,7 +327,7 @@ jobs:
|
||||
command: bundle exec fastlane android unsigned
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost-unsigned.apk"
|
||||
filename: "*.apk"
|
||||
|
||||
build-ios-beta:
|
||||
executor: ios
|
||||
@@ -341,7 +335,7 @@ jobs:
|
||||
- build-ios
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost_Beta.ipa"
|
||||
filename: "*.ipa"
|
||||
|
||||
build-ios-release:
|
||||
executor: ios
|
||||
@@ -349,7 +343,7 @@ jobs:
|
||||
- build-ios
|
||||
- persist
|
||||
- save:
|
||||
filename: "Mattermost.ipa"
|
||||
filename: "*.ipa"
|
||||
|
||||
build-ios-pr:
|
||||
executor: ios
|
||||
@@ -358,14 +352,13 @@ jobs:
|
||||
steps:
|
||||
- build-ios
|
||||
- save:
|
||||
filename: "Mattermost_Beta.ipa"
|
||||
filename: "*.ipa"
|
||||
|
||||
build-ios-unsigned:
|
||||
executor: ios
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- ruby-setup
|
||||
- npm-dependencies
|
||||
- pods-dependencies
|
||||
- assets
|
||||
@@ -381,16 +374,15 @@ jobs:
|
||||
- persist_to_workspace:
|
||||
root: ~/
|
||||
paths:
|
||||
- mattermost-mobile/Mattermost-unsigned.ipa
|
||||
- mattermost-mobile/*.ipa
|
||||
- save:
|
||||
filename: "Mattermost-unsigned.ipa"
|
||||
filename: "*.ipa"
|
||||
|
||||
build-ios-simulator:
|
||||
executor: ios
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/mattermost-mobile
|
||||
- ruby-setup
|
||||
- npm-dependencies
|
||||
- pods-dependencies
|
||||
- assets
|
||||
@@ -415,47 +407,42 @@ jobs:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- ruby-setup
|
||||
- deploy-to-store:
|
||||
task: "Deploy to Google Play"
|
||||
target: android
|
||||
file: Mattermost.apk
|
||||
file: "*.apk"
|
||||
|
||||
deploy-android-beta:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- ruby-setup
|
||||
- deploy-to-store:
|
||||
task: "Deploy to Google Play"
|
||||
target: android
|
||||
file: Mattermost_Beta.apk
|
||||
file: "*.apk"
|
||||
|
||||
deploy-ios-release:
|
||||
executor: ios
|
||||
steps:
|
||||
- ruby-setup
|
||||
- deploy-to-store:
|
||||
task: "Deploy to TestFlight"
|
||||
target: ios
|
||||
file: Mattermost.ipa
|
||||
file: "*.ipa"
|
||||
|
||||
deploy-ios-beta:
|
||||
executor: ios
|
||||
steps:
|
||||
- ruby-setup
|
||||
- deploy-to-store:
|
||||
task: "Deploy to TestFlight"
|
||||
target: ios
|
||||
file: Mattermost_Beta.ipa
|
||||
file: "*.ipa"
|
||||
|
||||
github-release:
|
||||
executor:
|
||||
name: android
|
||||
resource_class: medium
|
||||
steps:
|
||||
- ruby-setup
|
||||
- attach_workspace:
|
||||
at: ~/
|
||||
- run:
|
||||
|
||||
232
Makefile
232
Makefile
@@ -1,232 +0,0 @@
|
||||
.PHONY: pre-run pre-build clean
|
||||
.PHONY: check-style
|
||||
.PHONY: start stop
|
||||
.PHONY: run run-ios run-android
|
||||
.PHONY: build build-ios build-android unsigned-ios unsigned-android ios-sim-x86_64
|
||||
.PHONY: build-pr can-build-pr prepare-pr
|
||||
.PHONY: test help
|
||||
|
||||
OS := $(shell sh -c 'uname -s 2>/dev/null')
|
||||
BASE_ASSETS = $(shell find assets/base -type d) $(shell find assets/base -type f -name '*')
|
||||
OVERRIDE_ASSETS = $(shell find assets/override -type d 2> /dev/null) $(shell find assets/override -type f -name '*' 2> /dev/null)
|
||||
MM_UTILITIES_DIR = ../mattermost-utilities
|
||||
|
||||
node_modules: package.json
|
||||
@if ! [ $(shell which npm 2> /dev/null) ]; then \
|
||||
echo "npm is not installed https://npmjs.com"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
@echo Getting Javascript dependencies
|
||||
@npm install
|
||||
|
||||
npm-ci: package.json
|
||||
@if ! [ $(shell which npm 2> /dev/null) ]; then \
|
||||
echo "npm is not installed https://npmjs.com"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
@echo Getting Javascript dependencies
|
||||
@npm ci
|
||||
|
||||
.podinstall:
|
||||
ifeq ($(OS), Darwin)
|
||||
@echo "Required version of Cocoapods is not installed"
|
||||
@echo Installing gems;
|
||||
@bundle install
|
||||
@echo Getting Cocoapods dependencies;
|
||||
@cd ios && bundle exec pod install;
|
||||
endif
|
||||
@touch $@
|
||||
|
||||
dist/assets: $(BASE_ASSETS) $(OVERRIDE_ASSETS)
|
||||
@mkdir -p dist
|
||||
|
||||
@if [ -e dist/assets ] ; then \
|
||||
rm -rf dist/assets; \
|
||||
fi
|
||||
|
||||
@echo "Generating app assets"
|
||||
@node scripts/make-dist-assets.js
|
||||
|
||||
pre-run: | node_modules .podinstall dist/assets ## Installs dependencies and assets
|
||||
|
||||
pre-build: | npm-ci .podinstall dist/assets ## Install dependencies and assets before building
|
||||
|
||||
check-style: node_modules ## Runs eslint
|
||||
@echo Checking for style guide compliance
|
||||
@npm run check
|
||||
|
||||
clean: ## Cleans dependencies, previous builds and temp files
|
||||
@echo Cleaning started
|
||||
|
||||
@rm -f .podinstall
|
||||
@rm -rf ios/Pods
|
||||
@rm -rf node_modules
|
||||
@rm -rf dist
|
||||
@rm -rf ios/build
|
||||
@rm -rf android/app/build
|
||||
|
||||
@echo Cleanup finished
|
||||
|
||||
post-install:
|
||||
@./node_modules/.bin/patch-package
|
||||
@./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
|
||||
@sed -i'' -e 's|"./locale-data/index.js": false|"./locale-data/index.js": "./locale-data/index.js"|g' node_modules/react-intl/package.json
|
||||
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-messageformat/package.json
|
||||
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-relativeformat/package.json
|
||||
@sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json
|
||||
|
||||
start: | pre-run ## Starts the React Native packager server
|
||||
$(call start_packager)
|
||||
|
||||
stop: ## Stops the React Native packager server
|
||||
$(call stop_packager)
|
||||
|
||||
check-device-ios:
|
||||
@if ! [ $(shell which xcodebuild) ]; then \
|
||||
echo "xcode is not installed"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if ! [ $(shell which watchman) ]; then \
|
||||
echo "watchman is not installed"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
check-device-android:
|
||||
@if ! [ $(ANDROID_HOME) ]; then \
|
||||
echo "ANDROID_HOME is not set"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if ! [ $(shell which adb 2> /dev/null) ]; then \
|
||||
echo "adb is not installed"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
@echo "Connect your Android device or open the emulator"
|
||||
@adb wait-for-device
|
||||
|
||||
@if ! [ $(shell which watchman 2> /dev/null) ]; then \
|
||||
echo "watchman is not installed"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
prepare-android-build:
|
||||
@rm -rf ./node_modules/react-native/local-cli/templates/HelloWorld
|
||||
@rm -rf ./node_modules/react-native-linear-gradient/Examples/
|
||||
@rm -rf ./node_modules/react-native-orientation/demo/
|
||||
|
||||
run: run-ios ## alias for run-ios
|
||||
|
||||
run-ios: | check-device-ios pre-run ## Runs the app on an iOS simulator
|
||||
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
npm start & echo Running iOS app in development; \
|
||||
if [ ! -z "${SIMULATOR}" ]; then \
|
||||
react-native run-ios --simulator="${SIMULATOR}"; \
|
||||
else \
|
||||
react-native run-ios; \
|
||||
fi; \
|
||||
wait; \
|
||||
else \
|
||||
echo Running iOS app in development; \
|
||||
if [ ! -z "${SIMULATOR}" ]; then \
|
||||
react-native run-ios --simulator="${SIMULATOR}"; \
|
||||
else \
|
||||
react-native run-ios; \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
run-android: | check-device-android pre-run prepare-android-build ## Runs the app on an Android emulator or dev device
|
||||
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
npm start & echo Running Android app in development; \
|
||||
if [ ! -z ${VARIANT} ]; then \
|
||||
react-native run-android --no-packager --variant=${VARIANT}; \
|
||||
else \
|
||||
react-native run-android --no-packager; \
|
||||
fi; \
|
||||
wait; \
|
||||
else \
|
||||
echo Running Android app in development; \
|
||||
if [ ! -z ${VARIANT} ]; then \
|
||||
react-native run-android --no-packager --variant=${VARIANT}; \
|
||||
else \
|
||||
react-native run-android --no-packager; \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
build: | stop pre-build check-style ## Builds the app for Android & iOS
|
||||
$(call start_packager)
|
||||
@echo "Building App"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane build
|
||||
$(call stop_packager)
|
||||
|
||||
|
||||
build-ios: | stop pre-build check-style ## Builds the iOS app
|
||||
$(call start_packager)
|
||||
@echo "Building iOS app"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane ios build
|
||||
$(call stop_packager)
|
||||
|
||||
build-android: | stop pre-build check-style prepare-android-build ## Build the Android app
|
||||
$(call start_packager)
|
||||
@echo "Building Android app"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane android build
|
||||
$(call stop_packager)
|
||||
|
||||
unsigned-ios: stop pre-build check-style ## Build an unsigned version of the iOS app
|
||||
$(call start_packager)
|
||||
@cd fastlane && NODE_ENV=production bundle exec fastlane ios unsigned
|
||||
$(call stop_packager)
|
||||
|
||||
ios-sim-x86_64: stop pre-build check-style ## Build an unsigned x86_64 version of the iOS app for iPhone simulator
|
||||
$(call start_packager)
|
||||
@echo "Building unsigned x86_64 iOS app for iPhone simulator"
|
||||
@cd fastlane && NODE_ENV=production bundle exec fastlane ios simulator
|
||||
$(call stop_packager)
|
||||
|
||||
unsigned-android: stop pre-build check-style prepare-android-build ## Build an unsigned version of the Android app
|
||||
@cd fastlane && NODE_ENV=production bundle exec fastlane android unsigned
|
||||
|
||||
test: | pre-run check-style ## Runs tests
|
||||
@npm test
|
||||
|
||||
build-pr: | can-build-pr stop pre-build check-style ## Build a PR from the mattermost-mobile repo
|
||||
$(call start_packager)
|
||||
@echo "Building App from PR ${PR_ID}"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane build_pr pr:PR-${PR_ID}
|
||||
$(call stop_packager)
|
||||
|
||||
can-build-pr:
|
||||
@if [ -z ${PR_ID} ]; then \
|
||||
echo a PR number needs to be specified; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
## Help documentation https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||
help:
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
define start_packager
|
||||
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
npm start & echo; \
|
||||
else \
|
||||
echo React Native packager server already running; \
|
||||
fi
|
||||
endef
|
||||
|
||||
define stop_packager
|
||||
@echo Stopping React Native packager server
|
||||
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 1 ]; then \
|
||||
ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9; \
|
||||
echo React Native packager server stopped; \
|
||||
else \
|
||||
echo No React Native packager server running; \
|
||||
fi
|
||||
endef
|
||||
35
NOTICE.txt
35
NOTICE.txt
@@ -2577,41 +2577,6 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-v8
|
||||
|
||||
This product contains 'react-native-v8' by Kudo Chien.
|
||||
|
||||
Opt-in V8 runtime for React Native Android
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/Kudo/react-native-v8
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Kudo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-vector-icons
|
||||
|
||||
This product contains 'react-native-vector-icons' by Joel Arvidsson.
|
||||
|
||||
@@ -101,7 +101,7 @@ if (System.getenv("SENTRY_ENABLED") == "true") {
|
||||
* Upload all the APKs to the Play Store and people will download
|
||||
* the correct one based on the CPU architecture of their device.
|
||||
*/
|
||||
def enableSeparateBuildPerCPUArchitecture = false
|
||||
def enableSeparateBuildPerCPUArchitecture = project.hasProperty('separateApk') ? project.property('separateApk').toBoolean() : false
|
||||
|
||||
/**
|
||||
* Run Proguard to shrink the Java bytecode in release builds.
|
||||
@@ -132,12 +132,9 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
|
||||
versionCode 325
|
||||
versionName "1.35.0"
|
||||
versionCode 334
|
||||
versionName "1.37.0"
|
||||
multiDexEnabled = true
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
|
||||
}
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
@@ -155,7 +152,7 @@ android {
|
||||
abi {
|
||||
reset()
|
||||
enable enableSeparateBuildPerCPUArchitecture
|
||||
universalApk false // If true, also generate a universal APK
|
||||
universalApk enableSeparateBuildPerCPUArchitecture // If true, also generate a universal APK
|
||||
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
}
|
||||
@@ -184,7 +181,7 @@ android {
|
||||
def abi = output.getFilter(OutputFile.ABI)
|
||||
if (abi != null) { // null for the universal-debug, universal-release variants
|
||||
output.versionCodeOverride =
|
||||
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
|
||||
versionCodes.get(abi) * 1000000 + defaultConfig.versionCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
android/app/src/main/assets/fonts/compass-icons.ttf
Normal file
BIN
android/app/src/main/assets/fonts/compass-icons.ttf
Normal file
Binary file not shown.
@@ -82,7 +82,12 @@ public class RNPasteableActionCallback implements ActionMode.Callback {
|
||||
return null;
|
||||
}
|
||||
|
||||
String text = item.getText().toString();
|
||||
CharSequence chars = item.getText();
|
||||
if (chars == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String text = chars.toString();
|
||||
if (text.length() > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,4 @@ public class ShareActivity extends ReactActivity {
|
||||
MainApplication app = (MainApplication) this.getApplication();
|
||||
app.sharedExtensionIsOpened = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +186,7 @@ export function goToScreen(name, title, passProps = {}, options = {}) {
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
testID: 'screen.back.button',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
|
||||
@@ -188,6 +188,7 @@ describe('@actions/navigation', () => {
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
testID: 'screen.back.button',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
@@ -494,4 +495,4 @@ describe('@actions/navigation', () => {
|
||||
expect(popToRoot).toHaveBeenCalledWith(topComponentId);
|
||||
expect(EventEmitter.emit).toHaveBeenCalledWith(NavigationTypes.NAVIGATION_DISMISS_AND_POP_TO_ROOT);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import {ChannelTypes, RoleTypes, GroupTypes} from '@mm-redux/action_types';
|
||||
import {
|
||||
fetchMyChannelsAndMembers,
|
||||
getChannelByNameAndTeamName,
|
||||
joinChannel,
|
||||
leaveChannel as serviceLeaveChannel,
|
||||
} from '@mm-redux/actions/channels';
|
||||
import {savePreferences} from '@mm-redux/actions/preferences';
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
getCurrentChannelId,
|
||||
getRedirectChannelNameForTeam,
|
||||
getChannelsNameMapInTeam,
|
||||
getMyChannelMemberships,
|
||||
isManuallyUnread,
|
||||
} from '@mm-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
|
||||
@@ -44,15 +46,21 @@ export function loadChannelsByTeamName(teamName, errorHandler) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {currentTeamId} = state.entities.teams;
|
||||
const team = getTeamByName(state, teamName);
|
||||
|
||||
if (!team && errorHandler) {
|
||||
errorHandler();
|
||||
if (teamName) {
|
||||
const team = getTeamByName(state, teamName);
|
||||
|
||||
if (!team && errorHandler) {
|
||||
errorHandler();
|
||||
return {error: true};
|
||||
}
|
||||
|
||||
if (team && team.id !== currentTeamId) {
|
||||
await dispatch(fetchMyChannelsAndMembers(team.id));
|
||||
}
|
||||
}
|
||||
|
||||
if (team && team.id !== currentTeamId) {
|
||||
await dispatch(fetchMyChannelsAndMembers(team.id));
|
||||
}
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -210,13 +218,15 @@ export function handleSelectChannel(channelId) {
|
||||
|
||||
export function handleSelectChannelByName(channelName, teamName, errorHandler) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
let state = getState();
|
||||
const {teams: currentTeams, currentTeamId} = state.entities.teams;
|
||||
const currentTeam = currentTeams[currentTeamId];
|
||||
const currentTeamName = currentTeam?.name;
|
||||
const response = await dispatch(getChannelByNameAndTeamName(teamName || currentTeamName, channelName));
|
||||
const {error, data: channel} = response;
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
|
||||
state = getState();
|
||||
const reachable = getChannelReachable(state, channelName, teamName);
|
||||
|
||||
if (!reachable && errorHandler) {
|
||||
@@ -234,6 +244,17 @@ export function handleSelectChannelByName(channelName, teamName, errorHandler) {
|
||||
}
|
||||
|
||||
if (channel && currentChannelId !== channel.id) {
|
||||
if (channel.type === General.OPEN_CHANNEL) {
|
||||
const myMemberships = getMyChannelMemberships(state);
|
||||
if (!myMemberships[channel.id]) {
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
console.log('joining channel', channel?.display_name, channel.id); //eslint-disable-line
|
||||
const result = await dispatch(joinChannel(currentUserId, teamName, channel.id));
|
||||
if (result.error || !result.data || !result.data.channel) {
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatch(handleSelectChannel(channel.id));
|
||||
}
|
||||
|
||||
@@ -728,7 +749,7 @@ export function loadChannelsForTeam(teamId, skipDispatch = false) {
|
||||
};
|
||||
}
|
||||
|
||||
function loadSidebar(data) {
|
||||
export function loadSidebar(data) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {channels, channelMembers} = data;
|
||||
@@ -737,6 +758,8 @@ function loadSidebar(data) {
|
||||
if (sidebarActions.length) {
|
||||
dispatch(batchActions(sidebarActions, 'BATCH_LOAD_SIDEBAR'));
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
51
app/actions/views/permalink.ts
Normal file
51
app/actions/views/permalink.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {intlShape} from 'react-intl';
|
||||
import {Keyboard} from 'react-native';
|
||||
|
||||
import {showModalOverCurrentContext} from '@actions/navigation';
|
||||
import {loadChannelsByTeamName} from '@actions/views/channel';
|
||||
import {selectFocusedPostId} from '@mm-redux/actions/posts';
|
||||
import type {DispatchFunc} from '@mm-redux/types/actions';
|
||||
import {permalinkBadTeam} from '@utils/general';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
export let showingPermalink = false;
|
||||
|
||||
export function showPermalink(intl: typeof intlShape, teamName: string, postId: string, openAsPermalink = true) {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
const loadTeam = await dispatch(loadChannelsByTeamName(teamName, permalinkBadTeam.bind(null, intl)));
|
||||
|
||||
if (!loadTeam.error) {
|
||||
Keyboard.dismiss();
|
||||
dispatch(selectFocusedPostId(postId));
|
||||
|
||||
if (!showingPermalink) {
|
||||
const screen = 'Permalink';
|
||||
const passProps = {
|
||||
isPermalink: openAsPermalink,
|
||||
onClose: () => {
|
||||
dispatch(closePermalink());
|
||||
},
|
||||
};
|
||||
|
||||
const options = {
|
||||
layout: {
|
||||
componentBackgroundColor: changeOpacity('#000', 0.2),
|
||||
},
|
||||
};
|
||||
|
||||
showingPermalink = true;
|
||||
showModalOverCurrentContext(screen, passProps, options);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function closePermalink() {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
showingPermalink = false;
|
||||
return dispatch(selectFocusedPostId(''));
|
||||
};
|
||||
}
|
||||
@@ -51,10 +51,9 @@ exports[`AnnouncementBanner should match snapshot 1`] = `
|
||||
value="Banner Text"
|
||||
/>
|
||||
</Text>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
<CompassIcon
|
||||
color="#fff"
|
||||
name="info"
|
||||
name="info-outline"
|
||||
size={16}
|
||||
/>
|
||||
</ForwardRef>
|
||||
@@ -112,10 +111,9 @@ exports[`AnnouncementBanner should match snapshot 2`] = `
|
||||
value="Banner Text"
|
||||
/>
|
||||
</Text>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
<CompassIcon
|
||||
color="#fff"
|
||||
name="info"
|
||||
name="info-outline"
|
||||
size={16}
|
||||
/>
|
||||
</ForwardRef>
|
||||
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import RemoveMarkdown from 'app/components/remove_markdown';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import RemoveMarkdown from '@components/remove_markdown';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {goToScreen} from '@actions/navigation';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
@@ -122,9 +122,9 @@ export default class AnnouncementBanner extends PureComponent {
|
||||
>
|
||||
<RemoveMarkdown value={bannerText}/>
|
||||
</Text>
|
||||
<MaterialIcons
|
||||
<CompassIcon
|
||||
color={bannerTextColor}
|
||||
name='info'
|
||||
name='info-outline'
|
||||
size={16}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -1,44 +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 Svg, {
|
||||
G,
|
||||
Path,
|
||||
} from 'react-native-svg';
|
||||
|
||||
export default class AppIcon extends PureComponent {
|
||||
static propTypes = {
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
color: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Svg
|
||||
height={this.props.height}
|
||||
width={this.props.width}
|
||||
viewBox='0 0 500 500'
|
||||
>
|
||||
<G id='XMLID_1_'>
|
||||
<G id='XMLID_3_'>
|
||||
<Path
|
||||
id='XMLID_4_'
|
||||
class='st0'
|
||||
d='M396.9,47.7l2.6,53.1c43,47.5,60,114.8,38.6,178.1c-32,94.4-137.4,144.1-235.4,110.9 S51.1,253.1,83,158.7C104.5,95.2,159.2,52,222.5,40.5l34.2-40.4C150-2.8,49.3,63.4,13.3,169.9C-31,300.6,39.1,442.5,169.9,486.7 s272.6-25.8,316.9-156.6C522.7,223.9,483.1,110.3,396.9,47.7z'
|
||||
fill={this.props.color}
|
||||
/>
|
||||
</G>
|
||||
<Path
|
||||
id='XMLID_2_'
|
||||
class='st0'
|
||||
d='M335.6,204.3l-1.8-74.2l-1.5-42.7l-1-37c0,0,0.2-17.8-0.4-22c-0.1-0.9-0.4-1.6-0.7-2.2 c0-0.1-0.1-0.2-0.1-0.3c0-0.1-0.1-0.2-0.1-0.2c-0.7-1.2-1.8-2.1-3.1-2.6c-1.4-0.5-2.9-0.4-4.2,0.2c0,0-0.1,0-0.1,0 c-0.2,0.1-0.3,0.1-0.4,0.2c-0.6,0.3-1.2,0.7-1.8,1.3c-3,3-13.7,17.2-13.7,17.2l-23.2,28.8l-27.1,33l-46.5,57.8 c0,0-21.3,26.6-16.6,59.4s29.1,48.7,48,55.1c18.9,6.4,48,8.5,71.6-14.7C336.4,238.4,335.6,204.3,335.6,204.3z'
|
||||
fill={this.props.color}
|
||||
/>
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
38
app/components/app_version.tsx
Normal file
38
app/components/app_version.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
|
||||
const style = StyleSheet.create({
|
||||
info: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
version: {
|
||||
fontSize: 12,
|
||||
},
|
||||
});
|
||||
|
||||
const AppVersion = () => {
|
||||
return (
|
||||
<View pointerEvents='none'>
|
||||
<View style={style.info}>
|
||||
<FormattedText
|
||||
id='mobile.about.appVersion'
|
||||
defaultMessage='App Version: {version} (Build {number})'
|
||||
style={style.version}
|
||||
values={{
|
||||
version: DeviceInfo.getVersion(),
|
||||
number: DeviceInfo.getBuildNumber(),
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppVersion;
|
||||
@@ -6,13 +6,13 @@ import PropTypes from 'prop-types';
|
||||
import {StyleSheet, Text} from 'react-native';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import {intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
|
||||
import {showModal} from '@actions/navigation';
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import BottomSheet from '@utils/bottom_sheet';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
|
||||
export default class AtMention extends React.PureComponent {
|
||||
static propTypes = {
|
||||
@@ -57,7 +57,7 @@ export default class AtMention extends React.PureComponent {
|
||||
};
|
||||
|
||||
if (!this.closeButton) {
|
||||
this.closeButton = await MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor);
|
||||
this.closeButton = await CompassIcon.getImageSource('close', 24, theme.sidebarHeaderTextColor);
|
||||
}
|
||||
|
||||
const options = {
|
||||
|
||||
@@ -13,10 +13,9 @@ exports[`AttachmentButton should match snapshot 1`] = `
|
||||
}
|
||||
type="opacity"
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
<CompassIcon
|
||||
color="rgba(61,60,64,0.9)"
|
||||
name="md-add"
|
||||
name="plus"
|
||||
size={30}
|
||||
style={
|
||||
Object {
|
||||
|
||||
@@ -14,12 +14,12 @@ import RNFetchBlob from 'rn-fetch-blob';
|
||||
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 ImagePicker from 'react-native-image-picker';
|
||||
import Permissions from 'react-native-permissions';
|
||||
|
||||
import {showModalOverCurrentContext} from '@actions/navigation';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {NavigationTypes} from '@constants';
|
||||
import emmProvider from '@init/emm_provider';
|
||||
@@ -424,7 +424,7 @@ export default class AttachmentButton extends PureComponent {
|
||||
id: t('mobile.file_upload.camera_photo'),
|
||||
defaultMessage: 'Take Photo',
|
||||
},
|
||||
icon: 'camera',
|
||||
icon: 'camera-outline',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -435,7 +435,7 @@ export default class AttachmentButton extends PureComponent {
|
||||
id: t('mobile.file_upload.camera_video'),
|
||||
defaultMessage: 'Take Video',
|
||||
},
|
||||
icon: 'video-camera',
|
||||
icon: 'video-outline',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -446,7 +446,7 @@ export default class AttachmentButton extends PureComponent {
|
||||
id: t('mobile.file_upload.library'),
|
||||
defaultMessage: 'Photo Library',
|
||||
},
|
||||
icon: 'photo',
|
||||
icon: 'file-image-outline',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -457,7 +457,7 @@ export default class AttachmentButton extends PureComponent {
|
||||
id: t('mobile.file_upload.video'),
|
||||
defaultMessage: 'Video Library',
|
||||
},
|
||||
icon: 'file-video-o',
|
||||
icon: 'file-video-outline',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -468,7 +468,7 @@ export default class AttachmentButton extends PureComponent {
|
||||
id: t('mobile.file_upload.browse'),
|
||||
defaultMessage: 'Browse Files',
|
||||
},
|
||||
icon: 'file',
|
||||
icon: 'file-outline',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -503,11 +503,11 @@ export default class AttachmentButton extends PureComponent {
|
||||
style={style.buttonContainer}
|
||||
type={'opacity'}
|
||||
>
|
||||
<Icon
|
||||
<CompassIcon
|
||||
size={30}
|
||||
style={style.attachIcon}
|
||||
color={changeOpacity(theme.centerChannelColor, 0.9)}
|
||||
name='md-add'
|
||||
name='plus'
|
||||
/>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,6 @@ import {RequestStatus} from '@mm-redux/constants';
|
||||
|
||||
import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from 'app/constants/autocomplete';
|
||||
import AtMentionItem from 'app/components/autocomplete/at_mention_item';
|
||||
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
|
||||
import AutocompleteSectionHeader from 'app/components/autocomplete/autocomplete_section_header';
|
||||
import SpecialMentionItem from 'app/components/autocomplete/special_mention_item';
|
||||
import GroupMentionItem from 'app/components/autocomplete/at_mention_group/at_mention_group';
|
||||
@@ -62,22 +61,9 @@ export default class AtMention extends PureComponent {
|
||||
|
||||
// Not invoked, render nothing.
|
||||
if (matchTerm === null) {
|
||||
this.props.onResultCountChange(0);
|
||||
this.setState({
|
||||
mentionComplete: false,
|
||||
sections: [],
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.mentionComplete) {
|
||||
// Mention has been completed. Hide autocomplete.
|
||||
this.setState({
|
||||
sections: [],
|
||||
});
|
||||
|
||||
this.props.onResultCountChange(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -108,6 +94,12 @@ export default class AtMention extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.sections.length !== this.state.sections.length && this.state.sections.length === 0) {
|
||||
this.props.onResultCountChange(0);
|
||||
}
|
||||
}
|
||||
|
||||
buildSections = (props) => {
|
||||
const {isSearch, inChannel, outChannel, teamMembers, matchTerm, groups} = props;
|
||||
const sections = [];
|
||||
@@ -204,16 +196,20 @@ export default class AtMention extends PureComponent {
|
||||
}
|
||||
|
||||
onChangeText(completedDraft);
|
||||
this.setState({mentionComplete: true});
|
||||
this.setState({
|
||||
sections: [],
|
||||
});
|
||||
};
|
||||
|
||||
renderSectionHeader = ({section}) => {
|
||||
const isFirstSection = section.id === this.state.sections[0].id;
|
||||
return (
|
||||
<AutocompleteSectionHeader
|
||||
id={section.id}
|
||||
defaultMessage={section.defaultMessage}
|
||||
theme={this.props.theme}
|
||||
isLandscape={this.props.isLandscape}
|
||||
isFirstSection={isFirstSection}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -221,6 +217,7 @@ export default class AtMention extends PureComponent {
|
||||
renderItem = ({item}) => {
|
||||
return (
|
||||
<AtMentionItem
|
||||
testID={`autocomplete.at_mention.item.${item}`}
|
||||
onPress={this.completeMention}
|
||||
userId={item}
|
||||
/>
|
||||
@@ -253,8 +250,8 @@ export default class AtMention extends PureComponent {
|
||||
|
||||
render() {
|
||||
const {maxListHeight, theme, nestedScrollEnabled} = this.props;
|
||||
const {mentionComplete, sections} = this.state;
|
||||
if (sections.length === 0 || mentionComplete) {
|
||||
const {sections} = this.state;
|
||||
if (sections.length === 0) {
|
||||
// If we are not in an active state or the mention has been completed return null so nothing is rendered
|
||||
// other components are not blocked.
|
||||
return null;
|
||||
@@ -270,7 +267,6 @@ export default class AtMention extends PureComponent {
|
||||
sections={sections}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
ItemSeparatorComponent={AutocompleteDivider}
|
||||
initialNumToRender={10}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
/>
|
||||
@@ -282,6 +278,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
listView: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderRadius: 4,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
export default class GroupMentionItem extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -39,8 +39,8 @@ export default class GroupMentionItem extends PureComponent {
|
||||
type={'opacity'}
|
||||
>
|
||||
<View style={style.rowPicture}>
|
||||
<Icon
|
||||
name='users'
|
||||
<CompassIcon
|
||||
name='account-group-outline'
|
||||
style={style.rowIcon}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -12,7 +12,7 @@ import ProfilePicture from 'app/components/profile_picture';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {BotTag, GuestTag} from 'app/components/tag';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
|
||||
export default class AtMentionItem extends PureComponent {
|
||||
@@ -28,6 +28,8 @@ export default class AtMentionItem extends PureComponent {
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
isCurrentUser: PropTypes.bool.isRequired,
|
||||
showFullName: PropTypes.string,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -40,11 +42,24 @@ export default class AtMentionItem extends PureComponent {
|
||||
onPress(username);
|
||||
};
|
||||
|
||||
renderNameBlock = () => {
|
||||
let name = '';
|
||||
const {showFullName, firstName, lastName, nickname} = this.props;
|
||||
const hasNickname = nickname.length > 0;
|
||||
|
||||
if (showFullName === 'true') {
|
||||
name += `${firstName} ${lastName} `;
|
||||
}
|
||||
|
||||
if (hasNickname) {
|
||||
name += `(${nickname})`;
|
||||
}
|
||||
|
||||
return name.trim();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
firstName,
|
||||
lastName,
|
||||
nickname,
|
||||
userId,
|
||||
username,
|
||||
theme,
|
||||
@@ -52,49 +67,59 @@ export default class AtMentionItem extends PureComponent {
|
||||
isLandscape,
|
||||
isGuest,
|
||||
isCurrentUser,
|
||||
testID,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
const hasFullName = firstName.length > 0 && lastName.length > 0;
|
||||
const hasNickname = nickname.length > 0;
|
||||
const name = this.renderNameBlock();
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
testID={testID}
|
||||
key={userId}
|
||||
onPress={this.completeMention}
|
||||
style={[style.row, padding(isLandscape)]}
|
||||
type={'opacity'}
|
||||
style={padding(isLandscape)}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.rowPicture}>
|
||||
<ProfilePicture
|
||||
userId={userId}
|
||||
<View style={style.row}>
|
||||
<View style={style.rowPicture}>
|
||||
<ProfilePicture
|
||||
userId={userId}
|
||||
theme={theme}
|
||||
size={24}
|
||||
status={null}
|
||||
showStatus={false}
|
||||
/>
|
||||
</View>
|
||||
<BotTag
|
||||
show={isBot}
|
||||
theme={theme}
|
||||
size={20}
|
||||
status={null}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
{Boolean(name.length) &&
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{name}
|
||||
{isCurrentUser &&
|
||||
<FormattedText
|
||||
id='suggestion.mention.you'
|
||||
defaultMessage='(you)'
|
||||
/>}
|
||||
</Text>
|
||||
}
|
||||
<Text
|
||||
style={style.rowUsername}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{` @${username}`}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={style.rowUsername}>{`@${username}`}</Text>
|
||||
<BotTag
|
||||
show={isBot}
|
||||
theme={theme}
|
||||
/>
|
||||
<GuestTag
|
||||
show={isGuest}
|
||||
theme={theme}
|
||||
/>
|
||||
{hasFullName && <Text style={style.rowUsername}>{' - '}</Text>}
|
||||
<Text
|
||||
style={style.rowFullname}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{hasFullName && `${firstName} ${lastName}`}
|
||||
{hasNickname && ` (${nickname}) `}
|
||||
{isCurrentUser &&
|
||||
<FormattedText
|
||||
id='suggestion.mention.you'
|
||||
defaultMessage='(you)'
|
||||
/>}
|
||||
</Text>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
@@ -103,24 +128,29 @@ export default class AtMentionItem extends PureComponent {
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
row: {
|
||||
height: 40,
|
||||
paddingVertical: 8,
|
||||
paddingTop: 4,
|
||||
paddingHorizontal: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
rowPicture: {
|
||||
marginHorizontal: 8,
|
||||
width: 20,
|
||||
marginRight: 10,
|
||||
marginLeft: 2,
|
||||
width: 24,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
rowUsername: {
|
||||
fontSize: 13,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
rowFullname: {
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.6,
|
||||
paddingLeft: 4,
|
||||
},
|
||||
rowUsername: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 15,
|
||||
opacity: 0.56,
|
||||
flex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import {connect} from 'react-redux';
|
||||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
|
||||
import AtMentionItem from './at_mention_item';
|
||||
|
||||
@@ -14,12 +15,13 @@ import {isGuest} from 'app/utils/users';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const user = getUser(state, ownProps.userId);
|
||||
|
||||
const config = getConfig(state);
|
||||
return {
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
nickname: user.nickname,
|
||||
username: user.username,
|
||||
showFullName: config.ShowFullName,
|
||||
isBot: Boolean(user.is_bot),
|
||||
isGuest: isGuest(user),
|
||||
theme: getTheme(state),
|
||||
|
||||
@@ -36,8 +36,9 @@ export default class Autocomplete extends PureComponent {
|
||||
valueEvent: PropTypes.string,
|
||||
cursorPositionEvent: PropTypes.string,
|
||||
nestedScrollEnabled: PropTypes.bool,
|
||||
expandDown: PropTypes.bool,
|
||||
onVisible: PropTypes.func,
|
||||
offsetY: PropTypes.number,
|
||||
onKeyboardOffsetChanged: PropTypes.func,
|
||||
style: ViewPropTypes.style,
|
||||
};
|
||||
|
||||
@@ -47,6 +48,8 @@ export default class Autocomplete extends PureComponent {
|
||||
enableDateSuggestion: false,
|
||||
nestedScrollEnabled: false,
|
||||
onVisible: emptyFunction,
|
||||
onKeyboardOffsetChanged: emptyFunction,
|
||||
offsetY: 80,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
@@ -149,10 +152,12 @@ export default class Autocomplete extends PureComponent {
|
||||
keyboardDidShow = (e) => {
|
||||
const {height} = e.endCoordinates;
|
||||
this.setState({keyboardOffset: height});
|
||||
this.props.onKeyboardOffsetChanged(height);
|
||||
};
|
||||
|
||||
keyboardDidHide = () => {
|
||||
this.setState({keyboardOffset: 0});
|
||||
this.props.onKeyboardOffsetChanged(0);
|
||||
};
|
||||
|
||||
maxListHeight() {
|
||||
@@ -166,42 +171,37 @@ export default class Autocomplete extends PureComponent {
|
||||
offset = 90;
|
||||
}
|
||||
|
||||
maxHeight = this.props.deviceHeight - offset - this.state.keyboardOffset;
|
||||
maxHeight = (this.props.deviceHeight / 2) - offset;
|
||||
}
|
||||
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {theme, isSearch, expandDown} = this.props;
|
||||
const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount, cursorPosition, value} = this.state;
|
||||
const {theme, isSearch, offsetY} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
const maxListHeight = this.maxListHeight();
|
||||
const wrapperStyles = [];
|
||||
const containerStyles = [];
|
||||
const containerStyles = [style.borders];
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
wrapperStyles.push(style.shadow);
|
||||
}
|
||||
|
||||
if (isSearch) {
|
||||
wrapperStyles.push(style.base, style.searchContainer);
|
||||
containerStyles.push(style.content);
|
||||
wrapperStyles.push(style.base, style.searchContainer, {height: maxListHeight});
|
||||
} else {
|
||||
const containerStyle = expandDown ? style.containerExpandDown : style.container;
|
||||
const containerStyle = {bottom: offsetY};
|
||||
containerStyles.push(style.base, containerStyle);
|
||||
}
|
||||
|
||||
// We always need to render something, but we only draw the borders when we have results to show
|
||||
const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount, cursorPosition, value} = this.state;
|
||||
if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount > 0) {
|
||||
if (this.props.isSearch) {
|
||||
wrapperStyles.push(style.bordersSearch);
|
||||
} else {
|
||||
containerStyles.push(style.borders);
|
||||
}
|
||||
// Hide when there are no active autocompletes
|
||||
if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount === 0) {
|
||||
wrapperStyles.push(style.hidden);
|
||||
containerStyles.push(style.hidden);
|
||||
}
|
||||
|
||||
if (this.props.style) {
|
||||
containerStyles.push(this.props.style);
|
||||
}
|
||||
|
||||
const maxListHeight = this.maxListHeight();
|
||||
|
||||
return (
|
||||
<View style={wrapperStyles}>
|
||||
<View
|
||||
@@ -261,39 +261,37 @@ export default class Autocomplete extends PureComponent {
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
base: {
|
||||
left: 0,
|
||||
overflow: 'hidden',
|
||||
left: 8,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
right: 8,
|
||||
},
|
||||
borders: {
|
||||
borderWidth: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderBottomWidth: 0,
|
||||
overflow: 'hidden',
|
||||
borderRadius: 4,
|
||||
},
|
||||
bordersSearch: {
|
||||
borderWidth: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
},
|
||||
container: {
|
||||
bottom: 0,
|
||||
},
|
||||
containerExpandDown: {
|
||||
top: 0,
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
hidden: {
|
||||
display: 'none',
|
||||
},
|
||||
searchContainer: {
|
||||
flex: 1,
|
||||
...Platform.select({
|
||||
android: {
|
||||
top: 46,
|
||||
top: 42,
|
||||
},
|
||||
ios: {
|
||||
top: 44,
|
||||
top: 55,
|
||||
},
|
||||
}),
|
||||
},
|
||||
shadow: {
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 8,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 8,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,32 +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 {View} from 'react-native';
|
||||
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
|
||||
export default class AutocompleteDivider extends PureComponent {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<View style={style.divider}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import AutocompleteDivider from './autocomplete_divider';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(AutocompleteDivider);
|
||||
@@ -16,6 +16,7 @@ export default class AutocompleteSectionHeader extends PureComponent {
|
||||
loading: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
isFirstSection: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -23,12 +24,17 @@ export default class AutocompleteSectionHeader extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {defaultMessage, id, loading, theme, isLandscape} = this.props;
|
||||
const {defaultMessage, id, loading, theme, isLandscape, isFirstSection} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
const sectionStyles = [style.section, padding(isLandscape)];
|
||||
|
||||
if (!isFirstSection) {
|
||||
sectionStyles.push(style.borderTop);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.sectionWrapper}>
|
||||
<View style={[style.section, padding(isLandscape)]}>
|
||||
<View style={sectionStyles}>
|
||||
<FormattedText
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
@@ -48,18 +54,24 @@ export default class AutocompleteSectionHeader extends PureComponent {
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
section: {
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 8,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderTop: {
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
},
|
||||
section: {
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
top: -1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
sectionText: {
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.7),
|
||||
paddingVertical: 7,
|
||||
fontWeight: '600',
|
||||
textTransform: 'uppercase',
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
paddingTop: 16,
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 16,
|
||||
flex: 1,
|
||||
},
|
||||
sectionWrapper: {
|
||||
|
||||
@@ -91,12 +91,13 @@ export default class ChannelMention extends PureComponent {
|
||||
myMembers !== this.props.myMembers)) {
|
||||
const sections = [];
|
||||
if (isSearch) {
|
||||
if (directAndGroupMessages.length) {
|
||||
if (publicChannels.length) {
|
||||
sections.push({
|
||||
id: t('suggestion.search.direct'),
|
||||
defaultMessage: 'Direct Messages',
|
||||
data: directAndGroupMessages,
|
||||
key: 'directAndGroupMessages',
|
||||
id: t('suggestion.search.public'),
|
||||
defaultMessage: 'Public Channels',
|
||||
data: publicChannels.filter((cId) => myMembers[cId]),
|
||||
key: 'publicChannels',
|
||||
hideLoadingIndicator: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -110,13 +111,12 @@ export default class ChannelMention extends PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
if (publicChannels.length) {
|
||||
if (directAndGroupMessages.length) {
|
||||
sections.push({
|
||||
id: t('suggestion.search.public'),
|
||||
defaultMessage: 'Public Channels',
|
||||
data: publicChannels.filter((cId) => myMembers[cId]),
|
||||
key: 'publicChannels',
|
||||
hideLoadingIndicator: true,
|
||||
id: t('suggestion.search.direct'),
|
||||
defaultMessage: 'Direct Messages',
|
||||
data: directAndGroupMessages,
|
||||
key: 'directAndGroupMessages',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -184,6 +184,7 @@ export default class ChannelMention extends PureComponent {
|
||||
};
|
||||
|
||||
renderSectionHeader = ({section}) => {
|
||||
const isFirstSection = section.id === this.state.sections[0].id;
|
||||
return (
|
||||
<AutocompleteSectionHeader
|
||||
id={section.id}
|
||||
@@ -191,6 +192,7 @@ export default class ChannelMention extends PureComponent {
|
||||
loading={!section.hideLoadingIndicator && this.props.requestStatus === RequestStatus.STARTED}
|
||||
theme={this.props.theme}
|
||||
isLandscape={this.props.isLandscape}
|
||||
isFirstSection={isFirstSection}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -235,6 +237,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
listView: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderRadius: 4,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -5,14 +5,15 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
|
||||
import {BotTag, GuestTag} from 'app/components/tag';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {BotTag, GuestTag} from '@components/tag';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
export default class ChannelMentionItem extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -49,8 +50,12 @@ export default class ChannelMentionItem extends PureComponent {
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
let iconName = 'globe';
|
||||
let component;
|
||||
if (type === General.PRIVATE_CHANNEL) {
|
||||
iconName = 'lock';
|
||||
}
|
||||
|
||||
if (type === General.DM_CHANNEL || type === General.GM_CHANNEL) {
|
||||
if (!displayName) {
|
||||
return null;
|
||||
@@ -79,11 +84,18 @@ export default class ChannelMentionItem extends PureComponent {
|
||||
<TouchableWithFeedback
|
||||
key={channelId}
|
||||
onPress={this.completeMention}
|
||||
style={[style.row, padding(isLandscape)]}
|
||||
type={'opacity'}
|
||||
style={padding(isLandscape)}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<Text style={style.rowDisplayName}>{displayName}</Text>
|
||||
<Text style={style.rowName}>{` (~${name})`}</Text>
|
||||
<View style={style.row}>
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
style={style.icon}
|
||||
/>
|
||||
<Text style={style.rowDisplayName}>{displayName}</Text>
|
||||
<Text style={style.rowName}>{` ~${name}`}</Text>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
@@ -91,7 +103,6 @@ export default class ChannelMentionItem extends PureComponent {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{component}
|
||||
<AutocompleteDivider/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -99,19 +110,26 @@ export default class ChannelMentionItem extends PureComponent {
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
icon: {
|
||||
fontSize: 18,
|
||||
marginRight: 11,
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.56,
|
||||
},
|
||||
row: {
|
||||
padding: 8,
|
||||
paddingHorizontal: 16,
|
||||
height: 40,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
rowDisplayName: {
|
||||
fontSize: 13,
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
rowName: {
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.6,
|
||||
opacity: 0.56,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Dimensions, Platform, StyleSheet} from 'react-native';
|
||||
import {Dimensions, Platform, View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import {CalendarList, LocaleConfig} from 'react-native-calendars';
|
||||
import {intlShape} from 'react-intl';
|
||||
@@ -10,7 +10,7 @@ import {intlShape} from 'react-intl';
|
||||
import {memoizeResult} from '@mm-redux/utils/helpers';
|
||||
|
||||
import {DATE_MENTION_SEARCH_REGEX, ALL_SEARCH_FLAGS_REGEX} from 'app/constants/autocomplete';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
export default class DateSuggestion extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -37,6 +37,7 @@ export default class DateSuggestion extends PureComponent {
|
||||
|
||||
this.state = {
|
||||
mentionComplete: false,
|
||||
active: false,
|
||||
sections: [],
|
||||
};
|
||||
}
|
||||
@@ -45,18 +46,37 @@ export default class DateSuggestion extends PureComponent {
|
||||
this.setCalendarLocale();
|
||||
}
|
||||
|
||||
onLayout = (e) => {
|
||||
this.setState({calendarWidth: e.nativeEvent.layout.width});
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {locale, matchTerm} = this.props;
|
||||
const {locale, matchTerm, enableDateSuggestion} = this.props;
|
||||
const {mentionComplete} = this.state;
|
||||
|
||||
if ((matchTerm !== prevProps.matchTerm && matchTerm === null) || this.state.mentionComplete) {
|
||||
this.resetComponent();
|
||||
}
|
||||
|
||||
if (matchTerm === null || mentionComplete || !enableDateSuggestion) {
|
||||
this.setCalendarActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchTerm !== null) {
|
||||
this.props.onResultCountChange(1);
|
||||
this.setCalendarActive(true);
|
||||
}
|
||||
|
||||
if (locale !== prevProps.locale) {
|
||||
this.setCalendarLocale();
|
||||
}
|
||||
}
|
||||
|
||||
setCalendarActive = (active) => {
|
||||
this.setState({active});
|
||||
}
|
||||
|
||||
completeMention = (day) => {
|
||||
const mention = day.dateString;
|
||||
const {cursorPosition, onChangeText, value} = this.props;
|
||||
@@ -118,10 +138,11 @@ export default class DateSuggestion extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {mentionComplete} = this.state;
|
||||
const {matchTerm, enableDateSuggestion, theme} = this.props;
|
||||
const {active, calendarWidth} = this.state;
|
||||
const {theme} = this.props;
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
||||
if (matchTerm === null || mentionComplete || !enableDateSuggestion) {
|
||||
if (!active) {
|
||||
// If we are not in an active state or the mention has been completed return null so nothing is rendered
|
||||
// other components are not blocked.
|
||||
return null;
|
||||
@@ -131,22 +152,29 @@ export default class DateSuggestion extends PureComponent {
|
||||
const calendarStyle = calendarTheme(theme);
|
||||
|
||||
return (
|
||||
<CalendarList
|
||||
<View
|
||||
onLayout={this.onLayout}
|
||||
style={styles.calList}
|
||||
current={currentDate}
|
||||
maxDate={currentDate}
|
||||
pastScrollRange={24}
|
||||
futureScrollRange={0}
|
||||
scrollingEnabled={true}
|
||||
pagingEnabled={true}
|
||||
hideArrows={false}
|
||||
horizontal={true}
|
||||
showScrollIndicator={true}
|
||||
onDayPress={this.completeMention}
|
||||
showWeekNumbers={false}
|
||||
theme={calendarStyle}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
>
|
||||
{Boolean(calendarWidth) &&
|
||||
<CalendarList
|
||||
current={currentDate}
|
||||
maxDate={currentDate}
|
||||
pastScrollRange={24}
|
||||
futureScrollRange={0}
|
||||
scrollingEnabled={true}
|
||||
calendarWidth={calendarWidth}
|
||||
pagingEnabled={true}
|
||||
hideArrows={false}
|
||||
horizontal={true}
|
||||
showScrollIndicator={true}
|
||||
onDayPress={this.completeMention}
|
||||
showWeekNumbers={false}
|
||||
theme={calendarStyle}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -197,9 +225,13 @@ const calendarTheme = memoizeResult((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
calList: {
|
||||
height: 1700,
|
||||
paddingTop: 5,
|
||||
},
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
calList: {
|
||||
paddingTop: 5,
|
||||
width: '100%',
|
||||
borderRadius: 4,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 1`] = `n
|
||||
|
||||
exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
|
||||
<FlatList
|
||||
ItemSeparatorComponent={[Function]}
|
||||
data={
|
||||
Array [
|
||||
"+1",
|
||||
@@ -3044,6 +3043,8 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderRadius": 4,
|
||||
"paddingTop": 16,
|
||||
},
|
||||
Object {
|
||||
"maxHeight": undefined,
|
||||
|
||||
@@ -11,12 +11,11 @@ import {
|
||||
} from 'react-native';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import AutocompleteDivider from '@components/autocomplete/autocomplete_divider';
|
||||
import Emoji from '@components/emoji';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {BuiltInEmojis} from '@utils/emojis';
|
||||
import {getEmojiByName, compareEmojis} from '@utils/emoji_utils';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
const EMOJI_REGEX = /(^|\s|^\+|^-)(:([^:\s]*))$/i;
|
||||
const EMOJI_REGEX_WITHOUT_PREFIX = /\B(:([^:\s]*))$/i;
|
||||
@@ -150,21 +149,23 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
|
||||
renderItem = ({item}) => {
|
||||
const style = getStyleFromTheme(this.props.theme);
|
||||
|
||||
const completeSuggestion = () => this.completeSuggestion(item);
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={() => this.completeSuggestion(item)}
|
||||
style={style.row}
|
||||
type={'opacity'}
|
||||
onPress={completeSuggestion}
|
||||
underlayColor={changeOpacity(this.props.theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.emoji}>
|
||||
<Emoji
|
||||
emojiName={item}
|
||||
textStyle={style.emojiText}
|
||||
size={20}
|
||||
/>
|
||||
<View style={style.row}>
|
||||
<View style={style.emoji}>
|
||||
<Emoji
|
||||
emojiName={item}
|
||||
textStyle={style.emojiText}
|
||||
size={24}
|
||||
/>
|
||||
</View>
|
||||
<Text style={style.emojiName}>{`:${item}:`}</Text>
|
||||
</View>
|
||||
<Text style={style.emojiName}>{`:${item}:`}</Text>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
};
|
||||
@@ -199,12 +200,14 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
return values;
|
||||
}, []);
|
||||
const data = results.sort(sorter);
|
||||
this.props.onResultCountChange(data.length);
|
||||
this.setState({
|
||||
active: data.length > 0,
|
||||
dataSource: data,
|
||||
});
|
||||
}, 100);
|
||||
} else {
|
||||
this.props.onResultCountChange(emojis.length);
|
||||
this.setState({
|
||||
active: emojis.length > 0,
|
||||
dataSource: emojis.sort(sorter),
|
||||
@@ -229,7 +232,6 @@ export default class EmojiSuggestion extends PureComponent {
|
||||
data={this.state.dataSource}
|
||||
keyExtractor={this.keyExtractor}
|
||||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={AutocompleteDivider}
|
||||
pageSize={10}
|
||||
initialListSize={10}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
@@ -244,7 +246,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
marginRight: 5,
|
||||
},
|
||||
emojiName: {
|
||||
fontSize: 13,
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
emojiText: {
|
||||
@@ -252,14 +254,17 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
listView: {
|
||||
paddingTop: 16,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderRadius: 4,
|
||||
},
|
||||
row: {
|
||||
height: 40,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 8,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
overflow: 'hidden',
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 16,
|
||||
height: 40,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
|
||||
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import SlashSuggestionItem from './slash_suggestion_item';
|
||||
@@ -211,7 +210,6 @@ export default class SlashSuggestion extends PureComponent {
|
||||
data={this.state.dataSource}
|
||||
keyExtractor={this.keyExtractor}
|
||||
renderItem={this.renderItem}
|
||||
ItemSeparatorComponent={AutocompleteDivider}
|
||||
pageSize={10}
|
||||
initialListSize={10}
|
||||
nestedScrollEnabled={nestedScrollEnabled}
|
||||
@@ -225,6 +223,8 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
listView: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
paddingTop: 8,
|
||||
borderRadius: 4,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Text} from 'react-native';
|
||||
import {Image, Text, View} from 'react-native';
|
||||
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import slashIcon from '@assets/images/autocomplete/slash_command.png';
|
||||
|
||||
export default class SlashSuggestionItem extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -31,19 +32,44 @@ export default class SlashSuggestionItem extends PureComponent {
|
||||
hint,
|
||||
theme,
|
||||
suggestion,
|
||||
complete,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
let suggestionText = suggestion;
|
||||
if (suggestionText[0] === '/' && complete.split(' ').length === 1) {
|
||||
suggestionText = suggestionText.substring(1);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.completeSuggestion}
|
||||
style={[style.row, padding(isLandscape)]}
|
||||
type={'opacity'}
|
||||
style={padding(isLandscape)}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<Text style={style.suggestionName}>{`${suggestion} ${hint}`}</Text>
|
||||
<Text style={style.suggestionDescription}>{description}</Text>
|
||||
<View style={style.container}>
|
||||
<View style={style.icon}>
|
||||
<Image
|
||||
style={style.iconColor}
|
||||
width={10}
|
||||
height={16}
|
||||
source={slashIcon}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.suggestionContainer}>
|
||||
<Text style={style.suggestionName}>{`${suggestionText} ${hint}`}</Text>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={style.suggestionDescription}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
@@ -51,32 +77,38 @@ export default class SlashSuggestionItem extends PureComponent {
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
row: {
|
||||
paddingVertical: 8,
|
||||
icon: {
|
||||
fontSize: 24,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
width: 35,
|
||||
height: 35,
|
||||
marginRight: 12,
|
||||
borderRadius: 4,
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 8,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderLeftWidth: 1,
|
||||
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
alignItems: 'center',
|
||||
marginTop: 8,
|
||||
},
|
||||
rowDisplayName: {
|
||||
fontSize: 13,
|
||||
color: theme.centerChannelColor,
|
||||
iconColor: {
|
||||
tintColor: theme.centerChannelColor,
|
||||
},
|
||||
rowName: {
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.6,
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 16,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
suggestionContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
suggestionDescription: {
|
||||
fontSize: 11,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.6),
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
},
|
||||
suggestionName: {
|
||||
fontSize: 13,
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
marginBottom: 5,
|
||||
marginBottom: 4,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
export default class SpecialMentionItem extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -42,25 +42,27 @@ export default class SpecialMentionItem extends PureComponent {
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.completeMention}
|
||||
style={style.row}
|
||||
type={'opacity'}
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
type={'native'}
|
||||
>
|
||||
<View style={style.rowPicture}>
|
||||
<Icon
|
||||
name='users'
|
||||
style={style.rowIcon}
|
||||
/>
|
||||
<View style={style.row}>
|
||||
<View style={style.rowPicture}>
|
||||
<CompassIcon
|
||||
name='account-group-outline'
|
||||
style={style.rowIcon}
|
||||
/>
|
||||
</View>
|
||||
<Text style={style.textWrapper}>
|
||||
<Text style={style.rowUsername}>{`@${completeHandle}`}</Text>
|
||||
<Text style={style.rowUsername}>{' - '}</Text>
|
||||
<FormattedText
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
values={values}
|
||||
style={style.rowFullname}
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={style.textWrapper}>
|
||||
<Text style={style.rowUsername}>{`@${completeHandle}`}</Text>
|
||||
<Text style={style.rowUsername}>{' - '}</Text>
|
||||
<FormattedText
|
||||
id={id}
|
||||
defaultMessage={defaultMessage}
|
||||
values={values}
|
||||
style={style.rowFullname}
|
||||
/>
|
||||
</Text>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
@@ -68,10 +70,11 @@ export default class SpecialMentionItem extends PureComponent {
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
row: {
|
||||
height: 40,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 9,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
rowPicture: {
|
||||
marginHorizontal: 8,
|
||||
@@ -81,10 +84,10 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
},
|
||||
rowIcon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.7),
|
||||
fontSize: 14,
|
||||
fontSize: 18,
|
||||
},
|
||||
rowUsername: {
|
||||
fontSize: 13,
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
rowFullname: {
|
||||
|
||||
@@ -5,17 +5,17 @@ import React, {PureComponent} from 'react';
|
||||
import {Text, View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
import {ViewTypes} from '@constants';
|
||||
import {goToScreen} from '@actions/navigation';
|
||||
|
||||
export default class AutocompleteSelector extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -202,7 +202,7 @@ export default class AutocompleteSelector extends PureComponent {
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
<Icon
|
||||
<CompassIcon
|
||||
name='chevron-down'
|
||||
color={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
style={[style.icon, padding(isLandscape)]}
|
||||
|
||||
@@ -10,9 +10,8 @@ import {
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
import Icon from 'app/components/vector_icon';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
export default class ChannelIcon extends React.PureComponent {
|
||||
static propTypes = {
|
||||
@@ -82,48 +81,52 @@ export default class ChannelIcon extends React.PureComponent {
|
||||
let icon;
|
||||
if (isArchived) {
|
||||
icon = (
|
||||
<Icon
|
||||
name='archive'
|
||||
<CompassIcon
|
||||
name='archive-outline'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
|
||||
type='mattermost'
|
||||
testID='channel_icon.archive'
|
||||
/>
|
||||
);
|
||||
} else if (isBot) {
|
||||
icon = (
|
||||
<Icon
|
||||
name='bot'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: (size - 1), left: -1.5, top: -1}]}
|
||||
type='mattermost'
|
||||
<CompassIcon
|
||||
name='robot-happy'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, left: -1.5, top: -1}]}
|
||||
testID='channel_icon.bot'
|
||||
/>
|
||||
);
|
||||
} else if (hasDraft) {
|
||||
icon = (
|
||||
<Icon
|
||||
name='draft'
|
||||
<CompassIcon
|
||||
name='pencil-outline'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
|
||||
type='mattermost'
|
||||
testID='channel_icon.draft'
|
||||
/>
|
||||
);
|
||||
} else if (type === General.OPEN_CHANNEL) {
|
||||
icon = (
|
||||
<Icon
|
||||
name='public'
|
||||
<CompassIcon
|
||||
name='globe'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
|
||||
type='mattermost'
|
||||
testID='channel_icon.public'
|
||||
/>
|
||||
);
|
||||
} else if (type === General.PRIVATE_CHANNEL) {
|
||||
icon = (
|
||||
<Icon
|
||||
name='private'
|
||||
<CompassIcon
|
||||
name='lock-outline'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, left: 0.5}]}
|
||||
type='mattermost'
|
||||
testID='channel_icon.private'
|
||||
/>
|
||||
);
|
||||
} else if (type === General.GM_CHANNEL) {
|
||||
const fontSize = (size - 10);
|
||||
icon = (
|
||||
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: size + 1, height: size + 1}]}>
|
||||
<Text style={[style.group, unreadGroup, activeGroup, {fontSize: (size - 4)}]}>
|
||||
<View style={[style.groupBox, unreadGroupBox, activeGroupBox, {width: size, height: size}]}>
|
||||
<Text
|
||||
style={[style.group, unreadGroup, activeGroup, {fontSize}]}
|
||||
testID='channel_icon.gm_member_count'
|
||||
>
|
||||
{membersCount}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -132,37 +135,37 @@ export default class ChannelIcon extends React.PureComponent {
|
||||
switch (status) {
|
||||
case General.AWAY:
|
||||
icon = (
|
||||
<Icon
|
||||
name='away-avatar'
|
||||
<CompassIcon
|
||||
name='clock'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.awayIndicator}]}
|
||||
type='mattermost'
|
||||
testID='channel_icon.away'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case General.DND:
|
||||
icon = (
|
||||
<Icon
|
||||
name='dnd-avatar'
|
||||
<CompassIcon
|
||||
name='minus-circle'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.dndIndicator}]}
|
||||
type='mattermost'
|
||||
testID='channel_icon.dnd'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case General.ONLINE:
|
||||
icon = (
|
||||
<Icon
|
||||
name='online-avatar'
|
||||
<CompassIcon
|
||||
name='check-circle'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: theme.onlineIndicator}]}
|
||||
type='mattermost'
|
||||
testID='channel_icon.online'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
icon = (
|
||||
<Icon
|
||||
name='offline-avatar'
|
||||
<CompassIcon
|
||||
name='circle-outline'
|
||||
style={[style.icon, unreadIcon, activeIcon, {fontSize: size, color: offlineColor}]}
|
||||
type='mattermost'
|
||||
testID='channel_icon.offline'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
@@ -180,7 +183,7 @@ export default class ChannelIcon extends React.PureComponent {
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
marginRight: 12,
|
||||
marginRight: 8,
|
||||
alignItems: 'center',
|
||||
},
|
||||
icon: {
|
||||
|
||||
@@ -8,20 +8,20 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
import {showModal} from 'app/actions/navigation';
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {BotTag, GuestTag} from 'app/components/tag';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {isGuest} from 'app/utils/users';
|
||||
import {showModal} from '@actions/navigation';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {BotTag, GuestTag} from '@components/tag';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {t} from '@utils/i18n';
|
||||
import {isGuest} from '@utils/users';
|
||||
|
||||
class ChannelIntro extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -47,7 +47,7 @@ class ChannelIntro extends PureComponent {
|
||||
};
|
||||
|
||||
if (!this.closeButton) {
|
||||
this.closeButton = await MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor);
|
||||
this.closeButton = await CompassIcon.getImageSource('close', 24, theme.sidebarHeaderTextColor);
|
||||
}
|
||||
|
||||
const options = {
|
||||
@@ -91,6 +91,7 @@ class ChannelIntro extends PureComponent {
|
||||
<ProfilePicture
|
||||
userId={member.id}
|
||||
size={64}
|
||||
iconSize={48}
|
||||
statusBorderWidth={2}
|
||||
statusSize={25}
|
||||
/>
|
||||
|
||||
@@ -1,29 +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 Svg, {Path} from 'react-native-svg';
|
||||
|
||||
export default class CheckMark extends PureComponent {
|
||||
static propTypes = {
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
color: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Svg
|
||||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
viewBox='0 0 12 12'
|
||||
>
|
||||
<Path
|
||||
d='M11.667 1.756l-0.775-0.624c-0.382-0.307-0.604-0.304-0.932 0.101l-5.636 6.955 -2.623-2.179c-0.362-0.304-0.588-0.288-0.886 0.084l-0.598 0.779c-0.304 0.382-0.265 0.599 0.094 0.899l3.738 3.092c0.385 0.323 0.602 0.291 0.899-0.071l6.817-8.104c0.32-0.385 0.3-0.615-0.098-0.932Z'
|
||||
fill={this.props.color}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,13 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import {checkUpgradeType, isUpgradeAvailable} from 'app/utils/client_upgrade';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {showModal, dismissModal} from 'app/actions/navigation';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {DeviceTypes} from '@constants';
|
||||
import {checkUpgradeType, isUpgradeAvailable} from '@utils/client_upgrade';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {showModal, dismissModal} from '@actions/navigation';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class ClientUpgradeListener extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
|
||||
CompassIcon.getImageSource('close', 24, this.props.theme.sidebarHeaderTextColor).then((source) => {
|
||||
this.closeButton = source;
|
||||
});
|
||||
|
||||
@@ -117,11 +117,7 @@ export default class ClientUpgradeListener extends PureComponent {
|
||||
const {downloadLink} = this.props;
|
||||
const {intl} = this.context;
|
||||
|
||||
Linking.canOpenURL(downloadLink).then((supported) => {
|
||||
if (supported) {
|
||||
return Linking.openURL(downloadLink);
|
||||
}
|
||||
|
||||
Linking.openURL(downloadLink).catch(() => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.client_upgrade.download_error.title',
|
||||
@@ -132,8 +128,6 @@ export default class ClientUpgradeListener extends PureComponent {
|
||||
defaultMessage: 'An error occurred while trying to open the download link.',
|
||||
}),
|
||||
);
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.toggleUpgradeMessage(false);
|
||||
|
||||
7
app/components/compass_icon/index.js
Normal file
7
app/components/compass_icon/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createIconSetFromFontello} from 'react-native-vector-icons';
|
||||
import fontelloConfig from '@assets/compass-icons.json';
|
||||
|
||||
export default createIconSetFromFontello(fontelloConfig, 'compass-icons', 'compass-icons.ttf');
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
|
||||
import CustomListRow from 'app/components/custom_list/custom_list_row';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import CustomListRow from '@components/custom_list/custom_list_row';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
|
||||
export default class ChannelListRow extends React.PureComponent {
|
||||
static propTypes = {
|
||||
@@ -53,8 +53,8 @@ export default class ChannelListRow extends React.PureComponent {
|
||||
>
|
||||
<View style={style.container}>
|
||||
<View style={style.titleContainer}>
|
||||
<Icon
|
||||
name={this.props.isArchived ? 'archive' : 'globe'}
|
||||
<CompassIcon
|
||||
name={this.props.isArchived ? 'archive-outline' : 'globe'}
|
||||
style={style.icon}
|
||||
/>
|
||||
<Text style={style.displayName}>
|
||||
|
||||
@@ -7,10 +7,11 @@ import {
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import {paddingLeft as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import ConditionalTouchable from 'app/components/conditional_touchable';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {paddingLeft as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import ConditionalTouchable from '@components/conditional_touchable';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
|
||||
export default class CustomListRow extends React.PureComponent {
|
||||
static propTypes = {
|
||||
@@ -21,6 +22,7 @@ export default class CustomListRow extends React.PureComponent {
|
||||
children: CustomPropTypes.Children,
|
||||
item: PropTypes.object,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -34,15 +36,16 @@ export default class CustomListRow extends React.PureComponent {
|
||||
touchable={Boolean(this.props.enabled && this.props.onPress)}
|
||||
onPress={this.props.onPress}
|
||||
style={style.touchable}
|
||||
testID={this.props.testID}
|
||||
>
|
||||
<View style={[style.container, padding(this.props.isLandscape)]}>
|
||||
{this.props.selectable &&
|
||||
<View style={style.selectorContainer}>
|
||||
<View style={[style.selector, (this.props.selected && style.selectorFilled), (!this.props.enabled && style.selectorDisabled)]}>
|
||||
{this.props.selected &&
|
||||
<Icon
|
||||
<CompassIcon
|
||||
name='check'
|
||||
size={16}
|
||||
size={24}
|
||||
color='#fff'
|
||||
/>
|
||||
}
|
||||
@@ -92,7 +95,7 @@ const style = StyleSheet.create({
|
||||
backgroundColor: '#888',
|
||||
},
|
||||
selectorFilled: {
|
||||
backgroundColor: '#378FD2',
|
||||
backgroundColor: '#166DE0',
|
||||
borderWidth: 0,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@ export default class CustomList extends PureComponent {
|
||||
theme: PropTypes.object.isRequired,
|
||||
shouldRenderSeparator: PropTypes.bool,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -148,6 +149,7 @@ export default class CustomList extends PureComponent {
|
||||
scrollEventThrottle={60}
|
||||
style={style.list}
|
||||
stickySectionHeadersEnabled={true}
|
||||
testID={this.props.testID}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -201,6 +203,7 @@ export default class CustomList extends PureComponent {
|
||||
sections={data}
|
||||
style={style.list}
|
||||
stickySectionHeadersEnabled={false}
|
||||
testID={this.props.testID}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@ exports[`UserListRow should match snapshot 1`] = `
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
iconSize={24}
|
||||
size={32}
|
||||
userId="21345"
|
||||
/>
|
||||
@@ -159,6 +160,7 @@ exports[`UserListRow should match snapshot for currentUser with (you) populated
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
iconSize={24}
|
||||
size={32}
|
||||
userId="21345"
|
||||
/>
|
||||
@@ -289,6 +291,7 @@ exports[`UserListRow should match snapshot for deactivated user 1`] = `
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
iconSize={24}
|
||||
size={32}
|
||||
userId="21345"
|
||||
/>
|
||||
@@ -432,6 +435,7 @@ exports[`UserListRow should match snapshot for guest user 1`] = `
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
iconSize={24}
|
||||
size={32}
|
||||
userId="21345"
|
||||
/>
|
||||
|
||||
@@ -24,6 +24,7 @@ export default class UserListRow extends React.PureComponent {
|
||||
theme: PropTypes.object.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
teammateNameDisplay: PropTypes.string.isRequired,
|
||||
testID: PropTypes.string,
|
||||
...CustomListRow.propTypes,
|
||||
};
|
||||
|
||||
@@ -73,11 +74,13 @@ export default class UserListRow extends React.PureComponent {
|
||||
selectable={selectable}
|
||||
selected={selected}
|
||||
isLandscape={isLandscape}
|
||||
testID={this.props.testID}
|
||||
>
|
||||
<View style={style.profileContainer}>
|
||||
<ProfilePicture
|
||||
userId={id}
|
||||
size={32}
|
||||
iconSize={24}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.textContainer}>
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
import React, {PureComponent} from 'react';
|
||||
import {View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import AppIcon from 'app/components/app_icon';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {ViewTypes} from '@constants';
|
||||
|
||||
class DeletedPost extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -20,10 +21,10 @@ class DeletedPost extends PureComponent {
|
||||
return (
|
||||
<View style={style.main}>
|
||||
<View style={style.iconContainer}>
|
||||
<AppIcon
|
||||
<CompassIcon
|
||||
name='mattermost'
|
||||
color={theme.centerChannelColor}
|
||||
height={ViewTypes.PROFILE_PICTURE_SIZE}
|
||||
width={ViewTypes.PROFILE_PICTURE_SIZE}
|
||||
size={ViewTypes.PROFILE_PICTURE_SIZE}
|
||||
/>
|
||||
</View>
|
||||
<View style={style.textContainer}>
|
||||
|
||||
@@ -309,18 +309,28 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAwareScrollView>
|
||||
<KeyboardTrackingView
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"justifyContent": "flex-end",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"justifyContent": "flex-end",
|
||||
"position": "absolute",
|
||||
"width": "100%",
|
||||
},
|
||||
Object {
|
||||
"bottom": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<Connect(Autocomplete)
|
||||
cursorPosition={6}
|
||||
maxHeight={200}
|
||||
nestedScrollEnabled={true}
|
||||
offsetY={8}
|
||||
onChangeText={[Function]}
|
||||
onKeyboardOffsetChanged={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"position": undefined,
|
||||
@@ -328,6 +338,6 @@ exports[`EditChannelInfo should match snapshot 1`] = `
|
||||
}
|
||||
value="header"
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
@@ -9,11 +9,10 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scrollview';
|
||||
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
|
||||
|
||||
import {General} from '@mm-redux/constants';
|
||||
|
||||
import Autocomplete from 'app/components/autocomplete';
|
||||
import Autocomplete, {AUTOCOMPLETE_MAX_HEIGHT} from 'app/components/autocomplete';
|
||||
import ErrorText from 'app/components/error_text';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Loading from 'app/components/loading';
|
||||
@@ -71,6 +70,7 @@ export default class EditChannelInfo extends PureComponent {
|
||||
|
||||
this.state = {
|
||||
keyboardVisible: false,
|
||||
keyboardPosition: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -174,6 +174,10 @@ export default class EditChannelInfo extends PureComponent {
|
||||
this.setState({keyboardVisible: false});
|
||||
}
|
||||
|
||||
onKeyboardOffsetChanged = (keyboardPosition) => {
|
||||
this.setState({keyboardPosition});
|
||||
}
|
||||
|
||||
onHeaderFocus = () => {
|
||||
if (this.state.keyboardVisible) {
|
||||
this.scrollHeaderToTop();
|
||||
@@ -201,8 +205,13 @@ export default class EditChannelInfo extends PureComponent {
|
||||
error,
|
||||
saving,
|
||||
} = this.props;
|
||||
const {keyboardVisible} = this.state;
|
||||
|
||||
const {keyboardVisible, keyboardPosition} = this.state;
|
||||
const bottomStyle = {
|
||||
bottom: Platform.select({
|
||||
ios: keyboardPosition,
|
||||
android: 0,
|
||||
}),
|
||||
};
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const displayHeaderOnly = channelType === General.DM_CHANNEL ||
|
||||
@@ -354,16 +363,18 @@ export default class EditChannelInfo extends PureComponent {
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAwareScrollView>
|
||||
<KeyboardTrackingView style={style.autocompleteContainer}>
|
||||
<View style={[style.autocompleteContainer, bottomStyle]}>
|
||||
<Autocomplete
|
||||
cursorPosition={header.length}
|
||||
maxHeight={200}
|
||||
maxHeight={AUTOCOMPLETE_MAX_HEIGHT}
|
||||
onChangeText={this.onHeaderChangeText}
|
||||
value={header}
|
||||
nestedScrollEnabled={true}
|
||||
onKeyboardOffsetChanged={this.onKeyboardOffsetChanged}
|
||||
offsetY={8}
|
||||
style={style.autocomplete}
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -375,6 +386,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
position: undefined,
|
||||
},
|
||||
autocompleteContainer: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
container: {
|
||||
|
||||
@@ -2098,7 +2098,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
},
|
||||
],
|
||||
"defaultMessage": "PEOPLE",
|
||||
"icon": "smile-o",
|
||||
"icon": "emoticon-happy-outline",
|
||||
"id": "mobile.emoji_picker.people",
|
||||
"key": "people",
|
||||
},
|
||||
@@ -3182,7 +3182,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
},
|
||||
],
|
||||
"defaultMessage": "NATURE",
|
||||
"icon": "leaf",
|
||||
"icon": "leaf-outline",
|
||||
"id": "mobile.emoji_picker.nature",
|
||||
"key": "nature",
|
||||
},
|
||||
@@ -3778,7 +3778,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
},
|
||||
],
|
||||
"defaultMessage": "FOODS",
|
||||
"icon": "cutlery",
|
||||
"icon": "food-apple",
|
||||
"id": "mobile.emoji_picker.foods",
|
||||
"key": "foods",
|
||||
},
|
||||
@@ -4335,7 +4335,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
},
|
||||
],
|
||||
"defaultMessage": "ACTIVITY",
|
||||
"icon": "futbol-o",
|
||||
"icon": "basketball",
|
||||
"id": "mobile.emoji_picker.activity",
|
||||
"key": "activity",
|
||||
},
|
||||
@@ -5144,7 +5144,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
},
|
||||
],
|
||||
"defaultMessage": "PLACES",
|
||||
"icon": "plane",
|
||||
"icon": "airplane-variant",
|
||||
"id": "mobile.emoji_picker.places",
|
||||
"key": "places",
|
||||
},
|
||||
@@ -6323,7 +6323,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
},
|
||||
],
|
||||
"defaultMessage": "OBJECTS",
|
||||
"icon": "lightbulb-o",
|
||||
"icon": "lightbulb-outline",
|
||||
"id": "mobile.emoji_picker.objects",
|
||||
"key": "objects",
|
||||
},
|
||||
@@ -8155,7 +8155,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
},
|
||||
],
|
||||
"defaultMessage": "SYMBOLS",
|
||||
"icon": "heart-o",
|
||||
"icon": "heart-outline",
|
||||
"id": "mobile.emoji_picker.symbols",
|
||||
"key": "symbols",
|
||||
},
|
||||
@@ -9874,7 +9874,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
},
|
||||
],
|
||||
"defaultMessage": "FLAGS",
|
||||
"icon": "flag-o",
|
||||
"icon": "flag-outline",
|
||||
"id": "mobile.emoji_picker.flags",
|
||||
"key": "flags",
|
||||
},
|
||||
@@ -9952,7 +9952,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
},
|
||||
],
|
||||
"defaultMessage": "CUSTOM",
|
||||
"icon": "at",
|
||||
"icon": "emoticon-custom-outline",
|
||||
"id": "mobile.emoji_picker.custom",
|
||||
"key": "custom",
|
||||
},
|
||||
@@ -10008,9 +10008,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="smile-o"
|
||||
<CompassIcon
|
||||
name="emoticon-happy-outline"
|
||||
size={17}
|
||||
style={
|
||||
Array [
|
||||
@@ -10035,9 +10034,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="leaf"
|
||||
<CompassIcon
|
||||
name="leaf-outline"
|
||||
size={17}
|
||||
style={
|
||||
Array [
|
||||
@@ -10060,9 +10058,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="cutlery"
|
||||
<CompassIcon
|
||||
name="food-apple"
|
||||
size={17}
|
||||
style={
|
||||
Array [
|
||||
@@ -10085,9 +10082,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="futbol-o"
|
||||
<CompassIcon
|
||||
name="basketball"
|
||||
size={17}
|
||||
style={
|
||||
Array [
|
||||
@@ -10110,9 +10106,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="plane"
|
||||
<CompassIcon
|
||||
name="airplane-variant"
|
||||
size={17}
|
||||
style={
|
||||
Array [
|
||||
@@ -10135,9 +10130,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="lightbulb-o"
|
||||
<CompassIcon
|
||||
name="lightbulb-outline"
|
||||
size={17}
|
||||
style={
|
||||
Array [
|
||||
@@ -10160,9 +10154,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="heart-o"
|
||||
<CompassIcon
|
||||
name="heart-outline"
|
||||
size={17}
|
||||
style={
|
||||
Array [
|
||||
@@ -10185,9 +10178,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="flag-o"
|
||||
<CompassIcon
|
||||
name="flag-outline"
|
||||
size={17}
|
||||
style={
|
||||
Array [
|
||||
@@ -10210,9 +10202,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="at"
|
||||
<CompassIcon
|
||||
name="emoticon-custom-outline"
|
||||
size={17}
|
||||
style={
|
||||
Array [
|
||||
|
||||
@@ -12,6 +12,8 @@ import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
import {filterEmojiSearchInput} from './emoji_picker_base';
|
||||
import EmojiPicker from './emoji_picker.ios';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('components/emoji_picker/emoji_picker.ios', () => {
|
||||
const state = {
|
||||
...initialState,
|
||||
@@ -59,17 +61,17 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
test(`'${testCase.input}' should return '${testCase.output}'`, () => {
|
||||
test(`'${testCase.input}' should return '${testCase.output}'`, async () => {
|
||||
expect(filterEmojiSearchInput(testCase.input)).toEqual(testCase.output);
|
||||
});
|
||||
});
|
||||
|
||||
test('should match snapshot', () => {
|
||||
test('should match snapshot', async () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('searchEmojis should return the right values on fuse', () => {
|
||||
test('searchEmojis should return the right values on fuse', async () => {
|
||||
const input = '1';
|
||||
const output = ['100', '1234', '1st_place_medal', '+1', '-1', 'u7121'];
|
||||
|
||||
@@ -78,7 +80,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
|
||||
expect(result).toEqual(output);
|
||||
});
|
||||
|
||||
test('should set rebuildEmojis to true when deviceWidth changes', () => {
|
||||
test('should set rebuildEmojis to true when deviceWidth changes', async () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
@@ -90,7 +92,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
|
||||
expect(instance.rebuildEmojis).toBe(true);
|
||||
});
|
||||
|
||||
test('should rebuild emojis emojis when emojis change', () => {
|
||||
test('should rebuild emojis emojis when emojis change', async () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
const renderableEmojis = jest.spyOn(instance, 'renderableEmojis');
|
||||
@@ -103,7 +105,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
|
||||
expect(renderableEmojis).toHaveBeenCalledWith(baseProps.emojisBySection, baseProps.deviceWidth);
|
||||
});
|
||||
|
||||
test('should set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is true', () => {
|
||||
test('should set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is true', async () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
instance.setState = jest.fn();
|
||||
@@ -118,7 +120,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
|
||||
expect(instance.rebuildEmojis).toBe(false);
|
||||
});
|
||||
|
||||
test('should not set rebuilt emojis when rebuildEmojis is false and searchBarAnimationComplete is true', () => {
|
||||
test('should not set rebuilt emojis when rebuildEmojis is false and searchBarAnimationComplete is true', async () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
instance.setState = jest.fn();
|
||||
@@ -131,7 +133,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
|
||||
expect(instance.setState).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is false', () => {
|
||||
test('should not set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is false', async () => {
|
||||
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
|
||||
const instance = wrapper.instance();
|
||||
instance.setState = jest.fn();
|
||||
|
||||
@@ -13,21 +13,20 @@ import {
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
||||
import Octicons from 'react-native-vector-icons/Octicons';
|
||||
|
||||
import sectionListGetItemLayout from 'react-native-section-list-get-item-layout';
|
||||
|
||||
import Emoji from 'app/components/emoji';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import Emoji from '@components/emoji';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {DeviceTypes} from '@constants';
|
||||
import {emptyFunction} from '@utils/general';
|
||||
import {
|
||||
makeStyleSheetFromTheme,
|
||||
changeOpacity,
|
||||
} from 'app/utils/theme';
|
||||
import {compareEmojis} from 'app/utils/emoji_utils';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
} from '@utils/theme';
|
||||
import {compareEmojis} from '@utils/emoji_utils';
|
||||
import EmojiPickerRow from './emoji_picker_row';
|
||||
|
||||
const EMOJI_SIZE = 30;
|
||||
@@ -431,7 +430,7 @@ export default class EmojiPicker extends PureComponent {
|
||||
onPress={onPress}
|
||||
style={styles.sectionIconContainer}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
<CompassIcon
|
||||
name={section.icon}
|
||||
size={17}
|
||||
style={[styles.sectionIcon, (index === this.state.currentSectionIndex && styles.sectionIconHighlight)]}
|
||||
@@ -475,9 +474,9 @@ export default class EmojiPicker extends PureComponent {
|
||||
<View style={[styles.flex, styles.flexCenter]}>
|
||||
<View style={styles.flexCenter}>
|
||||
<View style={styles.notFoundIcon}>
|
||||
<Octicons
|
||||
name='search'
|
||||
size={60}
|
||||
<CompassIcon
|
||||
name='magnify'
|
||||
size={72}
|
||||
color={theme.buttonBg}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
View,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import Config from '@assets/config.json';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
|
||||
import GeneralError from './general_error';
|
||||
|
||||
@@ -98,7 +98,7 @@ export default class ErrorList extends PureComponent {
|
||||
onPress={() => this.props.actions.clearErrors()}
|
||||
>
|
||||
<View style={style.closeButton}>
|
||||
<Icon
|
||||
<CompassIcon
|
||||
name='close'
|
||||
size={10}
|
||||
color='#fff'
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
|
||||
const style = StyleSheet.create({
|
||||
buttonContainer: {
|
||||
@@ -54,7 +55,7 @@ function GeneralError(props) {
|
||||
style={style.buttonContainer}
|
||||
onPress={dismiss}
|
||||
>
|
||||
<Icon
|
||||
<CompassIcon
|
||||
name='close'
|
||||
size={20}
|
||||
color='#fff'
|
||||
|
||||
@@ -18,8 +18,10 @@ exports[`FileAttachment should match snapshot 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginHorizontal": 20,
|
||||
"marginVertical": 10,
|
||||
"marginBottom": 8.2,
|
||||
"marginLeft": 8,
|
||||
"marginRight": 6,
|
||||
"marginTop": 7.8,
|
||||
}
|
||||
}
|
||||
>
|
||||
@@ -28,14 +30,16 @@ exports[`FileAttachment should match snapshot 1`] = `
|
||||
type="opacity"
|
||||
>
|
||||
<FileAttachmentIcon
|
||||
defaultImage={false}
|
||||
failed={false}
|
||||
file={
|
||||
Object {
|
||||
"mime_type": "image/png",
|
||||
}
|
||||
}
|
||||
iconHeight={48}
|
||||
iconWidth={36}
|
||||
iconSize={48}
|
||||
onCaptureRef={[Function]}
|
||||
smallImage={false}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
@@ -65,8 +69,6 @@ exports[`FileAttachment should match snapshot 1`] = `
|
||||
"type": "Mattermost",
|
||||
}
|
||||
}
|
||||
wrapperHeight={48}
|
||||
wrapperWidth={36}
|
||||
/>
|
||||
</TouchableWithFeedbackIOS>
|
||||
</View>
|
||||
|
||||
@@ -246,8 +246,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
borderRadius: 5,
|
||||
},
|
||||
iconWrapper: {
|
||||
marginHorizontal: 20,
|
||||
marginVertical: 10,
|
||||
marginTop: 7.8,
|
||||
marginRight: 6,
|
||||
marginBottom: 8.2,
|
||||
marginLeft: 8,
|
||||
},
|
||||
circularProgress: {
|
||||
width: '100%',
|
||||
|
||||
@@ -26,8 +26,6 @@ import mattermostBucket from 'app/mattermost_bucket';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
import {ATTACHMENT_ICON_HEIGHT, ATTACHMENT_ICON_WIDTH} from 'app/constants/attachment';
|
||||
|
||||
const {DOCUMENTS_PATH} = DeviceTypes;
|
||||
const TEXT_PREVIEW_FORMATS = [
|
||||
'application/json',
|
||||
@@ -40,20 +38,9 @@ export default class FileAttachmentDocument extends PureComponent {
|
||||
static propTypes = {
|
||||
backgroundColor: PropTypes.string,
|
||||
canDownloadFiles: PropTypes.bool.isRequired,
|
||||
iconHeight: PropTypes.number,
|
||||
iconWidth: PropTypes.number,
|
||||
file: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onLongPress: PropTypes.func,
|
||||
wrapperHeight: PropTypes.number,
|
||||
wrapperWidth: PropTypes.number,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
iconHeight: ATTACHMENT_ICON_HEIGHT,
|
||||
iconWidth: ATTACHMENT_ICON_WIDTH,
|
||||
wrapperHeight: ATTACHMENT_ICON_HEIGHT,
|
||||
wrapperWidth: ATTACHMENT_ICON_WIDTH,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
@@ -322,17 +309,13 @@ export default class FileAttachmentDocument extends PureComponent {
|
||||
};
|
||||
|
||||
renderFileAttachmentIcon = () => {
|
||||
const {backgroundColor, iconHeight, iconWidth, file, theme, wrapperHeight, wrapperWidth} = this.props;
|
||||
const {backgroundColor, file, theme} = this.props;
|
||||
|
||||
return (
|
||||
<FileAttachmentIcon
|
||||
backgroundColor={backgroundColor}
|
||||
file={file.data}
|
||||
theme={theme}
|
||||
iconHeight={iconHeight}
|
||||
iconWidth={iconWidth}
|
||||
wrapperHeight={wrapperHeight}
|
||||
wrapperWidth={wrapperWidth}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,62 +4,70 @@
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Image,
|
||||
View,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
|
||||
import audioIcon from '@assets/images/icons/audio.png';
|
||||
import codeIcon from '@assets/images/icons/code.png';
|
||||
import excelIcon from '@assets/images/icons/excel.png';
|
||||
import genericIcon from '@assets/images/icons/generic.png';
|
||||
import imageIcon from '@assets/images/icons/image.png';
|
||||
import patchIcon from '@assets/images/icons/patch.png';
|
||||
import pdfIcon from '@assets/images/icons/pdf.png';
|
||||
import pptIcon from '@assets/images/icons/ppt.png';
|
||||
import textIcon from '@assets/images/icons/text.png';
|
||||
import videoIcon from '@assets/images/icons/video.png';
|
||||
import wordIcon from '@assets/images/icons/word.png';
|
||||
import {ATTACHMENT_ICON_HEIGHT, ATTACHMENT_ICON_WIDTH} from '@constants/attachment';
|
||||
import * as Utils from '@mm-redux/utils/file_utils';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
const ICON_PATH_FROM_FILE_TYPE = {
|
||||
audio: audioIcon,
|
||||
code: codeIcon,
|
||||
image: imageIcon,
|
||||
other: genericIcon,
|
||||
patch: patchIcon,
|
||||
pdf: pdfIcon,
|
||||
presentation: pptIcon,
|
||||
spreadsheet: excelIcon,
|
||||
text: textIcon,
|
||||
video: videoIcon,
|
||||
word: wordIcon,
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
|
||||
const BLUE_ICON = '#338AFF';
|
||||
const RED_ICON = '#ED522A';
|
||||
const GREEN_ICON = '#1CA660';
|
||||
const GRAY_ICON = '#999999';
|
||||
const FAILED_ICON_NAME_AND_COLOR = ['jumbo-attachment-image-broken', GRAY_ICON];
|
||||
const ICON_NAME_AND_COLOR_FROM_FILE_TYPE = {
|
||||
audio: ['jumbo-attachment-audio', BLUE_ICON],
|
||||
code: ['jumbo-attachment-code', BLUE_ICON],
|
||||
image: ['jumbo-attachment-image', BLUE_ICON],
|
||||
smallImage: ['image-outline', BLUE_ICON],
|
||||
other: ['jumbo-attachment-generic', BLUE_ICON],
|
||||
patch: ['jumbo-attachment-patch', BLUE_ICON],
|
||||
pdf: ['jumbo-attachment-pdf', RED_ICON],
|
||||
presentation: ['jumbo-attachment-powerpoint', RED_ICON],
|
||||
spreadsheet: ['jumbo-attachment-excel', GREEN_ICON],
|
||||
text: ['jumbo-attachment-text', GRAY_ICON],
|
||||
video: ['jumbo-attachment-video', BLUE_ICON],
|
||||
word: ['jumbo-attachment-word', BLUE_ICON],
|
||||
zip: ['jumbo-attachment-zip', BLUE_ICON],
|
||||
};
|
||||
|
||||
export default class FileAttachmentIcon extends PureComponent {
|
||||
static propTypes = {
|
||||
backgroundColor: PropTypes.string,
|
||||
file: PropTypes.object.isRequired,
|
||||
iconHeight: PropTypes.number,
|
||||
iconWidth: PropTypes.number,
|
||||
failed: PropTypes.bool,
|
||||
defaultImage: PropTypes.bool,
|
||||
smallImage: PropTypes.bool,
|
||||
file: PropTypes.object,
|
||||
onCaptureRef: PropTypes.func,
|
||||
wrapperHeight: PropTypes.number,
|
||||
wrapperWidth: PropTypes.number,
|
||||
iconColor: PropTypes.string,
|
||||
iconSize: PropTypes.number,
|
||||
theme: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
iconHeight: ATTACHMENT_ICON_HEIGHT,
|
||||
iconWidth: ATTACHMENT_ICON_WIDTH,
|
||||
wrapperHeight: ATTACHMENT_ICON_HEIGHT,
|
||||
wrapperWidth: ATTACHMENT_ICON_WIDTH,
|
||||
failed: false,
|
||||
defaultImage: false,
|
||||
smallImage: false,
|
||||
iconSize: 48,
|
||||
};
|
||||
|
||||
getFileIconPath(file) {
|
||||
getFileIconNameAndColor(file) {
|
||||
if (this.props.failed) {
|
||||
return FAILED_ICON_NAME_AND_COLOR;
|
||||
}
|
||||
|
||||
if (this.props.defaultImage) {
|
||||
if (this.props.smallImage) {
|
||||
return ICON_NAME_AND_COLOR_FROM_FILE_TYPE.smallImage;
|
||||
}
|
||||
|
||||
return ICON_NAME_AND_COLOR_FROM_FILE_TYPE.image;
|
||||
}
|
||||
|
||||
const fileType = Utils.getFileType(file);
|
||||
return ICON_PATH_FROM_FILE_TYPE[fileType] || ICON_PATH_FROM_FILE_TYPE.other;
|
||||
return ICON_NAME_AND_COLOR_FROM_FILE_TYPE[fileType] || ICON_NAME_AND_COLOR_FROM_FILE_TYPE.other;
|
||||
}
|
||||
|
||||
handleCaptureRef = (ref) => {
|
||||
@@ -71,18 +79,20 @@ export default class FileAttachmentIcon extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {backgroundColor, file, iconHeight, iconWidth, wrapperHeight, wrapperWidth, theme} = this.props;
|
||||
const source = this.getFileIconPath(file);
|
||||
const bgColor = backgroundColor || theme.centerChannelBg || 'transparent';
|
||||
const {backgroundColor, file, iconSize, theme, iconColor} = this.props;
|
||||
const [iconName, defaultIconColor] = this.getFileIconNameAndColor(file);
|
||||
const color = iconColor || defaultIconColor;
|
||||
const bgColor = backgroundColor || theme?.centerChannelBg || 'transparent';
|
||||
|
||||
return (
|
||||
<View
|
||||
ref={this.handleCaptureRef}
|
||||
style={[styles.fileIconWrapper, {backgroundColor: bgColor, height: wrapperHeight, width: wrapperWidth}]}
|
||||
style={[styles.fileIconWrapper, {backgroundColor: bgColor}]}
|
||||
>
|
||||
<Image
|
||||
style={{maxHeight: iconHeight, maxWidth: iconWidth, tintColor: changeOpacity(theme.centerChannelColor, 20)}}
|
||||
source={source}
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
size={iconSize}
|
||||
color={color}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -91,6 +101,8 @@ export default class FileAttachmentIcon extends PureComponent {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
fileIconWrapper: {
|
||||
flex: 1,
|
||||
borderRadius: 4,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
|
||||
@@ -8,11 +8,12 @@ import {
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
|
||||
import brokenImageIcon from '@assets/images/icons/brokenimage.png';
|
||||
import ProgressiveImage from '@components/progressive_image';
|
||||
import {Client4} from '@mm-redux/client';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
import FileAttachmentIcon from './file_attachment_icon';
|
||||
|
||||
const SMALL_IMAGE_MAX_HEIGHT = 48;
|
||||
const SMALL_IMAGE_MAX_WIDTH = 48;
|
||||
|
||||
@@ -36,10 +37,9 @@ export default class FileAttachmentImage extends PureComponent {
|
||||
theme: PropTypes.object,
|
||||
resizeMode: PropTypes.string,
|
||||
resizeMethod: PropTypes.string,
|
||||
wrapperHeight: PropTypes.number,
|
||||
wrapperWidth: PropTypes.number,
|
||||
isSingleImage: PropTypes.bool,
|
||||
imageDimensions: PropTypes.object,
|
||||
backgroundColor: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -72,11 +72,8 @@ export default class FileAttachmentImage extends PureComponent {
|
||||
|
||||
imageProps = (file) => {
|
||||
const imageProps = {};
|
||||
const {failed} = this.state;
|
||||
|
||||
if (failed) {
|
||||
imageProps.defaultSource = brokenImageIcon;
|
||||
} else if (file.localPath) {
|
||||
if (file.localPath) {
|
||||
imageProps.defaultSource = {uri: file.localPath};
|
||||
} else if (file.id) {
|
||||
imageProps.thumbnailUri = Client4.getFileThumbnailUrl(file.id);
|
||||
@@ -104,7 +101,10 @@ export default class FileAttachmentImage extends PureComponent {
|
||||
style={[
|
||||
wrapperStyle,
|
||||
style.smallImageBorder,
|
||||
{borderColor: changeOpacity(theme.centerChannelColor, 0.4)},
|
||||
{
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.4),
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{this.boxPlaceholder()}
|
||||
@@ -124,13 +124,26 @@ export default class FileAttachmentImage extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {failed} = this.state;
|
||||
const {
|
||||
file,
|
||||
imageDimensions,
|
||||
resizeMethod,
|
||||
resizeMode,
|
||||
backgroundColor,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
if (failed) {
|
||||
return (
|
||||
<FileAttachmentIcon
|
||||
failed={failed}
|
||||
backgroundColor={backgroundColor}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (file.height <= SMALL_IMAGE_MAX_HEIGHT || file.width <= SMALL_IMAGE_MAX_WIDTH) {
|
||||
return this.renderSmallImage();
|
||||
}
|
||||
@@ -169,7 +182,6 @@ const style = StyleSheet.create({
|
||||
paddingBottom: '100%',
|
||||
},
|
||||
smallImageBorder: {
|
||||
borderWidth: 1,
|
||||
borderRadius: 5,
|
||||
},
|
||||
smallImageOverlay: {
|
||||
|
||||
@@ -4,7 +4,6 @@ import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import {Client4} from '@mm-redux/client';
|
||||
import brokenImageIcon from '@assets/images/icons/brokenimage.png';
|
||||
|
||||
import FileAttachmentImage from './file_attachment_image.js';
|
||||
|
||||
@@ -27,15 +26,6 @@ describe('FileAttachmentImage', () => {
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
it('should have brokenImageIcon as defaultSource if state.failed is true', () => {
|
||||
wrapper.setState({failed: true});
|
||||
const file = {};
|
||||
const imageProps = instance.imageProps(file);
|
||||
expect(imageProps.defaultSource).toStrictEqual(brokenImageIcon);
|
||||
expect(imageProps.thumbnailUri).toBeUndefined();
|
||||
expect(imageProps.imageUri).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should have file.localPath as defaultSource if localPath is set', () => {
|
||||
wrapper.setState({failed: false});
|
||||
const file = {localPath: '/localPath.png'};
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
import {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
import {Alert} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {showModal} from 'app/actions/navigation';
|
||||
import {showModal} from '@actions/navigation';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
|
||||
export default class InteractiveDialogController extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -22,7 +22,7 @@ export default class InteractiveDialogController extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
MaterialIcon.getImageSource('close', 20, props.theme.sidebarHeaderTextColor).then((source) => {
|
||||
CompassIcon.getImageSource('close', 24, props.theme.sidebarHeaderTextColor).then((source) => {
|
||||
this.closeButton = source;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,9 +7,6 @@ import {shallow} from 'enzyme';
|
||||
import InteractiveDialogController from './interactive_dialog_controller';
|
||||
|
||||
jest.mock('react-intl');
|
||||
jest.mock('react-native-vector-icons/MaterialIcons', () => ({
|
||||
getImageSource: jest.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
describe('InteractiveDialogController', () => {
|
||||
test('should open interactive dialog as alert or screen depending on with or without element', () => {
|
||||
|
||||
@@ -16,6 +16,7 @@ export default class KeyboardLayout extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
style: CustomPropTypes.Style,
|
||||
testID: PropTypes.string,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -60,7 +61,10 @@ export default class KeyboardLayout extends PureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={layoutStyle}>
|
||||
<View
|
||||
style={layoutStyle}
|
||||
testID={this.props.testID}
|
||||
>
|
||||
{this.props.children}
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import CustomPropTypes from '@constants/custom_prop_types';
|
||||
|
||||
export default class MarkdownBlockQuote extends PureComponent {
|
||||
static propTypes = {
|
||||
@@ -22,10 +22,10 @@ export default class MarkdownBlockQuote extends PureComponent {
|
||||
let icon;
|
||||
if (!this.props.continue) {
|
||||
icon = (
|
||||
<Icon
|
||||
name='quote-left'
|
||||
<CompassIcon
|
||||
name='format-quote-open'
|
||||
style={this.props.iconStyle}
|
||||
size={14}
|
||||
size={20}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,16 +5,16 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Alert,
|
||||
Linking,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
|
||||
import brokenImageIcon from '@assets/images/icons/brokenimage.png';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import ImageViewPort from '@components/image_viewport';
|
||||
import ProgressiveImage from '@components/progressive_image';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
@@ -103,11 +103,19 @@ export default class MarkdownImage extends ImageViewPort {
|
||||
|
||||
handleLinkPress = () => {
|
||||
const url = normalizeProtocol(this.props.linkDestination);
|
||||
const {intl} = this.context;
|
||||
|
||||
Linking.canOpenURL(url).then((supported) => {
|
||||
if (supported) {
|
||||
Linking.openURL(url);
|
||||
}
|
||||
Linking.openURL(url).catch(() => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.link.error.title',
|
||||
defaultMessage: 'Error',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.link.error.text',
|
||||
defaultMessage: 'Unable to open the link.',
|
||||
}),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -215,9 +223,9 @@ export default class MarkdownImage extends ImageViewPort {
|
||||
}
|
||||
} else if (this.state.failed) {
|
||||
image = (
|
||||
<FastImage
|
||||
source={brokenImageIcon}
|
||||
style={style.brokenImageIcon}
|
||||
<CompassIcon
|
||||
name='jumbo-attachment-image-broken'
|
||||
size={24}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,22 +64,18 @@ export default class MarkdownLink extends PureComponent {
|
||||
onPermalinkPress(match.postId, match.teamName);
|
||||
}
|
||||
} else {
|
||||
Linking.canOpenURL(url).then((supported) => {
|
||||
if (supported) {
|
||||
Linking.openURL(url);
|
||||
} else {
|
||||
const {formatMessage} = this.context.intl;
|
||||
Alert.alert(
|
||||
formatMessage({
|
||||
id: 'mobile.server_link.error.title',
|
||||
defaultMessage: 'Link Error',
|
||||
}),
|
||||
formatMessage({
|
||||
id: 'mobile.server_link.error.text',
|
||||
defaultMessage: 'The link could not be found on this server.',
|
||||
}),
|
||||
);
|
||||
}
|
||||
Linking.openURL(url).catch(() => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
Alert.alert(
|
||||
formatMessage({
|
||||
id: 'mobile.server_link.error.title',
|
||||
defaultMessage: 'Link Error',
|
||||
}),
|
||||
formatMessage({
|
||||
id: 'mobile.server_link.error.text',
|
||||
defaultMessage: 'The link could not be found on this server.',
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,14 +11,14 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import {CELL_MAX_WIDTH, CELL_MIN_WIDTH} from 'app/components/markdown/markdown_table_cell/markdown_table_cell';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {CELL_MAX_WIDTH, CELL_MIN_WIDTH} from '@components/markdown/markdown_table_cell/markdown_table_cell';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {goToScreen} from '@actions/navigation';
|
||||
import {DeviceTypes} from '@constants';
|
||||
|
||||
const MAX_HEIGHT = 300;
|
||||
const MAX_PREVIEW_COLUMNS = 5;
|
||||
@@ -239,14 +239,14 @@ export default class MarkdownTable extends React.PureComponent {
|
||||
if (expandButtonOffset > 0) {
|
||||
expandButton = (
|
||||
<TouchableWithFeedback
|
||||
type={'opacity'}
|
||||
type='opacity'
|
||||
onPress={this.handlePress}
|
||||
style={[style.expandButton, {left: expandButtonOffset}]}
|
||||
>
|
||||
<View style={[style.iconContainer, {width: this.getTableWidth()}]}>
|
||||
<View style={style.iconButton}>
|
||||
<Icon
|
||||
name={'expand'}
|
||||
<CompassIcon
|
||||
name='arrow-expand'
|
||||
style={style.icon}
|
||||
/>
|
||||
</View>
|
||||
@@ -259,7 +259,7 @@ export default class MarkdownTable extends React.PureComponent {
|
||||
<TouchableWithFeedback
|
||||
style={style.tablePadding}
|
||||
onPress={this.handlePress}
|
||||
type={'opacity'}
|
||||
type='opacity'
|
||||
>
|
||||
<ScrollView
|
||||
onContentSizeChange={this.handleContentSizeChange}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Linking, Text, View} from 'react-native';
|
||||
import {Alert, Linking, Text, View} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
@@ -16,10 +17,27 @@ export default class AttachmentAuthor extends PureComponent {
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
openLink = () => {
|
||||
const {link} = this.props;
|
||||
if (link && Linking.canOpenURL(link)) {
|
||||
Linking.openURL(link);
|
||||
const {intl} = this.context;
|
||||
|
||||
if (link) {
|
||||
Linking.openURL(link).catch(() => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.link.error.title',
|
||||
defaultMessage: 'Error',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.link.error.text',
|
||||
defaultMessage: 'Unable to open the link.',
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Linking, Text, View} from 'react-native';
|
||||
import {Alert, Linking, Text, View} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import Markdown from 'app/components/markdown';
|
||||
@@ -15,10 +16,27 @@ export default class AttachmentTitle extends PureComponent {
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
openLink = () => {
|
||||
const {link} = this.props;
|
||||
if (link && Linking.canOpenURL(link)) {
|
||||
Linking.openURL(link);
|
||||
const {intl} = this.context;
|
||||
|
||||
if (link) {
|
||||
Linking.openURL(link).catch(() => {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.link.error.title',
|
||||
defaultMessage: 'Error',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.link.error.text',
|
||||
defaultMessage: 'Unable to open the link.',
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -54,10 +54,9 @@ exports[`AttachmentFooter matches snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
<CompassIcon
|
||||
color="#FFFFFF"
|
||||
name="md-checkmark"
|
||||
name="check"
|
||||
size={20}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -12,18 +12,19 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
import {RequestStatus} from '@mm-redux/constants';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {DeviceTypes, ViewTypes} from '@constants';
|
||||
import {INDICATOR_BAR_HEIGHT} from '@constants/view';
|
||||
import networkConnectionListener, {checkConnection} from '@utils/network';
|
||||
import {t} from '@utils/i18n';
|
||||
|
||||
import mattermostBucket from 'app/mattermost_bucket';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import networkConnectionListener, {checkConnection} from 'app/utils/network';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
const MAX_WEBSOCKET_RETRIES = 3;
|
||||
const CONNECTION_RETRY_SECONDS = 5;
|
||||
@@ -371,9 +372,9 @@ export default class NetworkIndicator extends PureComponent {
|
||||
defaultMessage = 'Connected';
|
||||
action = (
|
||||
<View style={styles.actionContainer}>
|
||||
<IonIcon
|
||||
<CompassIcon
|
||||
color='#FFFFFF'
|
||||
name='md-checkmark'
|
||||
name='check'
|
||||
size={20}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -3,18 +3,15 @@
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Image, Text, View} from 'react-native';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {Text, View} from 'react-native';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
export default class NoResults extends PureComponent {
|
||||
static propTypes = {
|
||||
description: PropTypes.string,
|
||||
iconName: PropTypes.string,
|
||||
iconType: PropTypes.oneOf(['' /* image */, 'ion', 'material-community']),
|
||||
image: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
|
||||
iconName: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
};
|
||||
@@ -23,45 +20,19 @@ export default class NoResults extends PureComponent {
|
||||
const {
|
||||
description,
|
||||
iconName,
|
||||
iconType,
|
||||
image,
|
||||
theme,
|
||||
title,
|
||||
} = this.props;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
let icon;
|
||||
if (image) {
|
||||
icon = (
|
||||
<Image
|
||||
source={image}
|
||||
style={{width: 37, height: 37, tintColor: changeOpacity(theme.centerChannelColor, 0.5)}}
|
||||
/>
|
||||
);
|
||||
} else if (iconName) {
|
||||
if (iconType === 'ion') {
|
||||
icon = (
|
||||
<IonIcon
|
||||
size={72}
|
||||
name={iconName}
|
||||
style={style.icon}
|
||||
/>
|
||||
);
|
||||
} else if (iconType === 'material-community') {
|
||||
icon = (
|
||||
<MaterialCommunityIcons
|
||||
size={72}
|
||||
name={iconName}
|
||||
style={style.icon}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<View style={style.iconContainer}>
|
||||
{icon}
|
||||
<CompassIcon
|
||||
size={72}
|
||||
name={iconName}
|
||||
style={style.icon}
|
||||
/>
|
||||
</View>
|
||||
<Text style={style.title}>{title}</Text>
|
||||
{description &&
|
||||
|
||||
@@ -27,7 +27,17 @@ export class PasteableTextInput extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
getLastSubscriptionKey = () => {
|
||||
const subscriptions = OnPasteEventEmitter._subscriber._subscriptionsForType.onPaste?.filter((sub) => sub); // eslint-disable-line no-underscore-dangle
|
||||
return subscriptions?.length && subscriptions[subscriptions.length - 1].key;
|
||||
}
|
||||
|
||||
onPaste = (event) => {
|
||||
const lastSubscriptionKey = this.getLastSubscriptionKey();
|
||||
if (this.subscription.key !== lastSubscriptionKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = null;
|
||||
let error = null;
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import {PasteableTextInput} from './index';
|
||||
const nativeEventEmitter = new NativeEventEmitter();
|
||||
|
||||
describe('PasteableTextInput', () => {
|
||||
const emit = jest.spyOn(EventEmitter, 'emit');
|
||||
|
||||
test('should render pasteable text input', () => {
|
||||
const onPaste = jest.fn();
|
||||
const text = 'My Text';
|
||||
@@ -24,12 +26,11 @@ describe('PasteableTextInput', () => {
|
||||
test('should call onPaste props if native onPaste trigger', () => {
|
||||
const event = {someData: 'data'};
|
||||
const text = 'My Text';
|
||||
const onPaste = jest.spyOn(EventEmitter, 'emit');
|
||||
shallow(
|
||||
<PasteableTextInput>{text}</PasteableTextInput>,
|
||||
);
|
||||
nativeEventEmitter.emit('onPaste', event);
|
||||
expect(onPaste).toHaveBeenCalledWith(PASTE_FILES, null, event);
|
||||
expect(emit).toHaveBeenCalledWith(PASTE_FILES, null, event);
|
||||
});
|
||||
|
||||
test('should remove onPaste listener when unmount', () => {
|
||||
@@ -43,4 +44,16 @@ describe('PasteableTextInput', () => {
|
||||
component.instance().componentWillUnmount();
|
||||
expect(mockRemove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should emit PASTE_FILES event only for last subscription', () => {
|
||||
const component1 = shallow(<PasteableTextInput/>);
|
||||
const instance1 = component1.instance();
|
||||
const component2 = shallow(<PasteableTextInput/>);
|
||||
const instance2 = component2.instance();
|
||||
|
||||
instance1.onPaste();
|
||||
expect(emit).not.toHaveBeenCalled();
|
||||
instance2.onPaste();
|
||||
expect(emit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
ViewPropTypes,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import {Posts} from '@mm-redux/constants';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
@@ -18,6 +17,7 @@ import {isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from '@mm-redux
|
||||
|
||||
import {showModalOverCurrentContext, showModal} from '@actions/navigation';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import PostBody from '@components/post_body';
|
||||
import PostHeader from '@components/post_header';
|
||||
import PostProfilePicture from '@components/post_profile_picture';
|
||||
@@ -102,7 +102,7 @@ export default class Post extends PureComponent {
|
||||
};
|
||||
|
||||
if (!this.closeButton) {
|
||||
this.closeButton = await MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor);
|
||||
this.closeButton = await CompassIcon.getImageSource('close', 24, theme.sidebarHeaderTextColor);
|
||||
}
|
||||
|
||||
const options = {
|
||||
|
||||
@@ -9,24 +9,25 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import {Posts} from '@mm-redux/constants';
|
||||
|
||||
import CombinedSystemMessage from 'app/components/combined_system_message';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import CombinedSystemMessage from '@components/combined_system_message';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import Markdown from '@components/markdown';
|
||||
import MarkdownEmoji from '@components/markdown/markdown_emoji';
|
||||
import ShowMoreButton from '@components/show_more_button';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
|
||||
import {emptyFunction} from '@utils/general';
|
||||
import {getMarkdownTextStyles, getMarkdownBlockStyles} from '@utils/markdown';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {showModalOverCurrentContext} from '@actions/navigation';
|
||||
|
||||
import telemetry from '@telemetry';
|
||||
|
||||
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';
|
||||
import ShowMoreButton from 'app/components/show_more_button';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
|
||||
import telemetry from 'app/telemetry';
|
||||
|
||||
let FileAttachmentList;
|
||||
let PostAddChannelMember;
|
||||
@@ -71,7 +72,7 @@ export default class PostBody extends PureComponent {
|
||||
shouldRenderJumboEmoji: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object,
|
||||
location: PropTypes.string,
|
||||
mentionKeys: PropTypes.array.isRequired,
|
||||
mentionKeys: PropTypes.array,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -79,6 +80,7 @@ export default class PostBody extends PureComponent {
|
||||
onFailedPostPress: emptyFunction,
|
||||
onPress: emptyFunction,
|
||||
replyBarStyle: [],
|
||||
mentionKeys: [],
|
||||
message: '',
|
||||
postProps: {},
|
||||
};
|
||||
@@ -457,8 +459,8 @@ export default class PostBody extends PureComponent {
|
||||
style={style.retry}
|
||||
type={'opacity'}
|
||||
>
|
||||
<Icon
|
||||
name='ios-information-circle-outline'
|
||||
<CompassIcon
|
||||
name='information-outline'
|
||||
size={26}
|
||||
color={theme.errorTextColor}
|
||||
/>
|
||||
|
||||
@@ -16,7 +16,7 @@ const renderUsername = (value = '') => {
|
||||
};
|
||||
|
||||
const renderMessage = (postBodyProps, styles, intl, localeHolder, values, skipMarkdown = false) => {
|
||||
const {onPress} = postBodyProps;
|
||||
const {onPress, onPermalinkPress} = postBodyProps;
|
||||
const {messageStyle, textStyles} = styles;
|
||||
|
||||
if (skipMarkdown) {
|
||||
@@ -32,6 +32,7 @@ const renderMessage = (postBodyProps, styles, intl, localeHolder, values, skipMa
|
||||
baseTextStyle={messageStyle}
|
||||
disableAtChannelMentionHighlight={true}
|
||||
onPostPress={onPress}
|
||||
onPermalinkPress={onPermalinkPress}
|
||||
textStyles={textStyles}
|
||||
value={intl.formatMessage(localeHolder, values)}
|
||||
/>
|
||||
|
||||
@@ -291,6 +291,7 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
||||
<View>
|
||||
<TextInput
|
||||
allowFontScaling={true}
|
||||
autoCompleteType="off"
|
||||
blurOnSubmit={false}
|
||||
disableFullscreenUI={true}
|
||||
keyboardAppearance="light"
|
||||
@@ -316,6 +317,7 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
||||
}
|
||||
}
|
||||
testID="post_input"
|
||||
textContentType="none"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
<View
|
||||
@@ -366,8 +368,6 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
||||
}
|
||||
>
|
||||
<View
|
||||
forwardedRef={[Function]}
|
||||
isInteraction={true}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
@@ -398,25 +398,6 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
accessible={true}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
"opacity": 1,
|
||||
"padding": 10,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<View
|
||||
accessible={true}
|
||||
focusable={true}
|
||||
@@ -436,23 +417,10 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Image
|
||||
source={
|
||||
Object {
|
||||
"testUri": "../../../dist/assets/images/icons/slash-forward-box.png",
|
||||
}
|
||||
}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"height": 24,
|
||||
"opacity": 1,
|
||||
"tintColor": "rgba(61,60,64,0.64)",
|
||||
"width": 24,
|
||||
},
|
||||
null,
|
||||
]
|
||||
}
|
||||
<Icon
|
||||
color="rgba(61,60,64,0.64)"
|
||||
name="at"
|
||||
size={24}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
@@ -473,7 +441,13 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
||||
"padding": 10,
|
||||
}
|
||||
}
|
||||
/>
|
||||
>
|
||||
<Icon
|
||||
color="rgba(61,60,64,0.64)"
|
||||
name="slash-forward-box-outline"
|
||||
size={24}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
accessible={true}
|
||||
focusable={true}
|
||||
@@ -492,7 +466,13 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
||||
"padding": 10,
|
||||
}
|
||||
}
|
||||
/>
|
||||
>
|
||||
<Icon
|
||||
color="rgba(61,60,64,0.64)"
|
||||
name="file-document-outline"
|
||||
size={24}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
accessible={true}
|
||||
focusable={true}
|
||||
@@ -511,7 +491,38 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
||||
"padding": 10,
|
||||
}
|
||||
}
|
||||
/>
|
||||
>
|
||||
<Icon
|
||||
color="rgba(61,60,64,0.64)"
|
||||
name="image-outline"
|
||||
size={24}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
accessible={true}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
"opacity": 1,
|
||||
"padding": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
color="rgba(61,60,64,0.64)"
|
||||
name="camera-outline"
|
||||
size={24}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
@@ -539,44 +550,11 @@ exports[`PostDraft Should render the DraftInput 1`] = `
|
||||
]
|
||||
}
|
||||
>
|
||||
<RNSVGSvgView
|
||||
align="xMidYMid"
|
||||
bbHeight={16}
|
||||
bbWidth={19}
|
||||
focusable={false}
|
||||
height={16}
|
||||
meetOrSlice={0}
|
||||
minX={0}
|
||||
minY={0}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "transparent",
|
||||
"borderWidth": 0,
|
||||
},
|
||||
Object {
|
||||
"flex": 0,
|
||||
"height": 16,
|
||||
"width": 19,
|
||||
},
|
||||
]
|
||||
}
|
||||
vbHeight={14}
|
||||
vbWidth={16}
|
||||
width={19}
|
||||
>
|
||||
<RNSVGGroup>
|
||||
<RNSVGPath
|
||||
d="M1.09222552,5.76354703 L0.405413926,0.94983436 C0.379442059,0.7702001 0.452067095,0.59056584 0.594431403,0.479386798 C0.737276671,0.368693254 0.927737029,0.343932856 1.09318744,0.414815564 C3.7432798,1.54942439 12.1192069,5.13676911 15.0920238,6.40974487 C15.2723839,6.48693905 15.3892573,6.66560231 15.3892573,6.8632 C15.3892573,7.06079769 15.2723839,7.23946095 15.0920238,7.31665513 C12.0961208,8.59982635 3.6105347,12.2337789 1.0316245,13.3378013 C0.878198098,13.4033436 0.701685594,13.3810106 0.569902417,13.2785706 C0.43763828,13.1761305 0.37030381,13.0096047 0.393870874,12.8430789 L1.07875863,7.96479496 L8.93669128,6.8632 L1.09222552,5.76354703 Z"
|
||||
fill={2164260863}
|
||||
propList={
|
||||
Array [
|
||||
"fill",
|
||||
]
|
||||
}
|
||||
/>
|
||||
</RNSVGGroup>
|
||||
</RNSVGSvgView>
|
||||
<Icon
|
||||
color="rgba(255,255,255,0.5)"
|
||||
name="send"
|
||||
size={24}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -607,6 +585,17 @@ exports[`PostDraft Should render the ReadOnly for canPost 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
color="#3d3c40"
|
||||
name="glasses"
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 20,
|
||||
"lineHeight": 22,
|
||||
"opacity": 0.56,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
@@ -645,6 +634,17 @@ exports[`PostDraft Should render the ReadOnly for channelIsReadOnly 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
color="#3d3c40"
|
||||
name="glasses"
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 20,
|
||||
"lineHeight": 22,
|
||||
"opacity": 0.56,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
|
||||
@@ -415,16 +415,6 @@ export default class DraftInput extends PureComponent {
|
||||
theme={theme}
|
||||
registerTypingAnimation={registerTypingAnimation}
|
||||
/>
|
||||
{Platform.OS === 'android' &&
|
||||
<Autocomplete
|
||||
cursorPositionEvent={cursorPositionEvent}
|
||||
maxHeight={Math.min(this.state.top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
|
||||
onChangeText={this.handleInputQuickAction}
|
||||
valueEvent={valueEvent}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
/>
|
||||
}
|
||||
<View
|
||||
style={[style.inputWrapper, padding(isLandscape)]}
|
||||
onLayout={this.handleLayout}
|
||||
@@ -475,6 +465,16 @@ export default class DraftInput extends PureComponent {
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
{Platform.OS === 'android' &&
|
||||
<Autocomplete
|
||||
cursorPositionEvent={cursorPositionEvent}
|
||||
maxHeight={Math.min(this.state.top - AUTOCOMPLETE_MARGIN, AUTOCOMPLETE_MAX_HEIGHT)}
|
||||
onChangeText={this.handleInputQuickAction}
|
||||
valueEvent={valueEvent}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -513,4 +513,4 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.20),
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,8 @@ import {renderWithReduxIntl} from 'test/testing_library';
|
||||
|
||||
import PostDraft from './post_draft';
|
||||
|
||||
jest.mock('app/components/compass_icon', () => 'Icon');
|
||||
|
||||
const mockStore = configureMockStore([thunk]);
|
||||
const state = {
|
||||
...intitialState,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
exports[`PostInput should match, full snapshot 1`] = `
|
||||
<ForwardRef(WrappedPasteableTextInput)
|
||||
autoCompleteType="off"
|
||||
blurOnSubmit={false}
|
||||
disableFullscreenUI={true}
|
||||
keyboardAppearance="light"
|
||||
@@ -24,6 +25,7 @@ exports[`PostInput should match, full snapshot 1`] = `
|
||||
"paddingTop": 6,
|
||||
}
|
||||
}
|
||||
textContentType="none"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`;
|
||||
|
||||
@@ -290,6 +290,8 @@ export default class PostInput extends PureComponent {
|
||||
keyboardType={this.state.keyboardType}
|
||||
onEndEditing={this.handleEndEditing}
|
||||
disableFullscreenUI={true}
|
||||
textContentType='none'
|
||||
autoCompleteType='off'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -12,8 +12,7 @@ exports[`CameraButton should match snapshot 1`] = `
|
||||
}
|
||||
type="opacity"
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
<CompassIcon
|
||||
color="rgba(61,60,64,0.64)"
|
||||
name="camera-outline"
|
||||
size={24}
|
||||
|
||||
@@ -10,10 +10,10 @@ import {
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import Permissions from 'react-native-permissions';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {NavigationTypes} from '@constants';
|
||||
import {ICON_SIZE, MAX_FILE_COUNT_WARNING} from '@constants/post_draft';
|
||||
@@ -159,7 +159,7 @@ export default class CameraQuickAction extends PureComponent {
|
||||
style={style.icon}
|
||||
type={'opacity'}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
<CompassIcon
|
||||
color={color}
|
||||
name='camera-outline'
|
||||
size={ICON_SIZE}
|
||||
|
||||
@@ -12,8 +12,7 @@ exports[`FileQuickAction should match snapshot 1`] = `
|
||||
}
|
||||
type="opacity"
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
<CompassIcon
|
||||
color="rgba(61,60,64,0.64)"
|
||||
name="file-document-outline"
|
||||
size={24}
|
||||
|
||||
@@ -6,10 +6,10 @@ import {intlShape} from 'react-intl';
|
||||
import {Alert, NativeModules, Platform, StyleSheet} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import AndroidOpenSettings from 'react-native-android-open-settings';
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import Permissions from 'react-native-permissions';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {NavigationTypes} from '@constants';
|
||||
import {ICON_SIZE, MAX_FILE_COUNT_WARNING} from '@constants/post_draft';
|
||||
@@ -151,7 +151,7 @@ export default class FileQuickAction extends PureComponent {
|
||||
style={style.icon}
|
||||
type={'opacity'}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
<CompassIcon
|
||||
color={color}
|
||||
name='file-document-outline'
|
||||
size={ICON_SIZE}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user