forked from Ivasoft/mattermost-mobile
Compare commits
15 Commits
release-1.
...
v1.5.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b8c36171c | ||
|
|
87cb6d68db | ||
|
|
71654ccfe9 | ||
|
|
3da18b4dae | ||
|
|
db09e1376a | ||
|
|
2d5dba1fc0 | ||
|
|
7c3f4b9659 | ||
|
|
666e99dd76 | ||
|
|
fbf0f8344f | ||
|
|
08743639bf | ||
|
|
a4bdda15ea | ||
|
|
27b3001fd3 | ||
|
|
78a80c4afe | ||
|
|
d55766f039 | ||
|
|
8eaf11d284 |
@@ -40,7 +40,7 @@
|
||||
"brace-style": [2, "1tbs", { "allowSingleLine": false }],
|
||||
"camelcase": [2, {"properties": "never"}],
|
||||
"class-methods-use-this": 0,
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"comma-dangle": [2, "never"],
|
||||
"comma-spacing": [2, {"before": false, "after": true}],
|
||||
"comma-style": [2, "last"],
|
||||
"complexity": [1, 10],
|
||||
@@ -54,7 +54,7 @@
|
||||
"eqeqeq": [2, "smart"],
|
||||
"func-call-spacing": [2, "never"],
|
||||
"func-names": 2,
|
||||
"func-style": [2, "declaration", { "allowArrowFunctions": true }],
|
||||
"func-style": [2, "declaration"],
|
||||
"generator-star-spacing": [0, {"before": false, "after": true}],
|
||||
"global-require": 2,
|
||||
"guard-for-in": 2,
|
||||
|
||||
@@ -37,12 +37,12 @@ suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-3]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
[version]
|
||||
^0.56.0
|
||||
^0.53.0
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -82,6 +82,3 @@ ios/sentry.properties
|
||||
# Pods
|
||||
.podinstall
|
||||
ios/Pods/
|
||||
|
||||
#editor-settings
|
||||
.vscode
|
||||
96
CHANGELOG.md
96
CHANGELOG.md
@@ -1,101 +1,5 @@
|
||||
# Mattermost Mobile Apps Changelog
|
||||
|
||||
## v1.6.1 Release
|
||||
- Release Date: February 13, 2018
|
||||
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed an issue preventing the app from going to the correct channel when opened from a push notification
|
||||
- Fixed an issue on Android devices where the app could sometimes freeze on the launch screen
|
||||
- Fixed an issue on Samsung devices causing extra letters to be insterted when typing to filter user lists
|
||||
|
||||
## v1.6.0 Release
|
||||
- Release Date: February 6, 2018
|
||||
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
|
||||
|
||||
### Highlights
|
||||
|
||||
#### Android File Sharing
|
||||
- Share files and images from other applications as attached files in Mattermost
|
||||
|
||||
### Improvements
|
||||
- Added a right drawer to access settings, edit profile information, change online status and logout
|
||||
- Added support for opening a Direct Message channel with yourself
|
||||
|
||||
### Bugs
|
||||
- Fixed a number of issues causing crashes on Android devices
|
||||
- Fixed an issue with auto capitalization on Android keyboards
|
||||
- Fixed an issue where the GitLab SSO login button sometimes didn't appear
|
||||
- Fixed an issue with link previews not appearing on some accounts
|
||||
- Fixed an issue where logging out of the app didn't clear the notification badge on the homescreen icon
|
||||
- Fixed an issue where interactive message buttons would not wrap to a new line
|
||||
- Fixed an issue where the keyboard would sometimes overlap the text input box
|
||||
- Fixed an issue where the Direct Message channel wouldn't open from the profile page
|
||||
- Fixed an issue where posts would sometimes overlap
|
||||
- Fixed an issue where the app sometimes hangs on logout
|
||||
|
||||
## v1.5.3 Release
|
||||
- Release Date: February 1, 2018
|
||||
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
|
||||
- Fixed a login issue when connecting to servers running a Data Retention policy
|
||||
|
||||
## v1.5.2 Release
|
||||
- Release Date: January 12, 2018
|
||||
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed an issue causing some Android devices to crash on launch
|
||||
- Fixed an issue with the app occasionally crashing when receiving push notifications in a new channel
|
||||
- Channel footer area is now refreshed when switching between Group and Direct Message channels
|
||||
- Fixed an issue on some Android devices so Mattermost verifies it has permissions to access ringtones
|
||||
- Fixed an issue where the text box overlapped the keyboard on some iOS devices using multiple keyboard layouts
|
||||
- Fixed an issue with video uploads on Android devices
|
||||
- Fixed an issue with GIF uploads on iOS devices
|
||||
- Fixed an issue with the mention badge flickering on the channel drawer icon when there were over 10 unread mentions
|
||||
- Fixed an issue with the app occasionally freezing when requesting the RefreshToken
|
||||
|
||||
## v1.5.1 Release
|
||||
|
||||
- Release Date: December 7, 2017
|
||||
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed an issue with the upgrade app screen showing with a transparent background
|
||||
- Fixed an issue with clearing or replying to notifications sometimes crashing the app on Android
|
||||
- Fixed an issue with the app sometimes crashing due to a missing function in the swiping control
|
||||
|
||||
## v1.5 Release
|
||||
|
||||
- Release Date: December 6, 2017
|
||||
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
|
||||
|
||||
### Highlights
|
||||
|
||||
#### File Viewer
|
||||
- Preview videos, RTF, PDFs, Word, Excel, and Powerpoint files
|
||||
|
||||
#### iPhone X Compatibility
|
||||
- Added support for iPhone X
|
||||
|
||||
#### Slash Commands
|
||||
- Added support for using custom slash commands
|
||||
- Added support for built-in slash commands /away, /online, /offline, /dnd, /header, /purpose, /kick, /me, /shrug
|
||||
|
||||
### Improvements
|
||||
- In iOS, 3D touch can now be used to peek into a channel to view the contents, and quickly mark it as read
|
||||
- Markdown images in posts now render
|
||||
- Copy posts, URLs, and code blocks
|
||||
- Opening a channel with Unread messages takes you to the "New Messages" indicator
|
||||
- Support for data retention, interactive message buttons, and viewing Do Not Disturb statuses depending on the server version
|
||||
- (Edited) indicator now shows up beside edited posts
|
||||
- Added a "Recently Used" section for emoji reactions
|
||||
|
||||
### Bug Fixes
|
||||
- Android notifications now follow the default system setting for vibration
|
||||
- Fixed app crashing when opening notification settings on Android
|
||||
- Fixed an issue where the "Proceed" button on sign in screen stopped working after pressing logout multiple times
|
||||
- HEIC images posted from iPhones now get converted to JPEG before uploading
|
||||
|
||||
## v1.4.1 Release
|
||||
|
||||
Release Date: Nov 15, 2017
|
||||
|
||||
14
Jenkinsfile
vendored
14
Jenkinsfile
vendored
@@ -1,14 +0,0 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
stages {
|
||||
stage('Test') {
|
||||
steps {
|
||||
echo 'assets/base/config.json'
|
||||
sh 'cat assets/base/config.json'
|
||||
sh 'touch .podinstall'
|
||||
sh 'make test || exit 1'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
251
Makefile
251
Makefile
@@ -1,18 +1,15 @@
|
||||
.PHONY: pre-run clean
|
||||
.PHONY: check-style
|
||||
.PHONY: start stop
|
||||
.PHONY: run run-ios run-android
|
||||
.PHONY: build-ios build-android unsigned-ios unsigned-android
|
||||
.PHONY: test help
|
||||
.PHONY: run run-ios run-android check-style test clean post-install start stop
|
||||
.PHONY: check-ios-target build-ios
|
||||
.PHONY: check-android-target prepare-android-build build-android
|
||||
.PHONY: start-packager stop-packager
|
||||
|
||||
POD := $(shell which pod 2> /dev/null)
|
||||
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)
|
||||
ios_target := $(filter-out build-ios,$(MAKECMDGOALS))
|
||||
android_target := $(filter-out build-android,$(MAKECMDGOALS))
|
||||
POD := $(shell command -v pod 2> /dev/null)
|
||||
|
||||
.yarninstall: package.json
|
||||
@if ! [ $(shell which yarn 2> /dev/null) ]; then \
|
||||
echo "yarn is not installed https://yarnpkg.com"; \
|
||||
@if ! [ $(shell command -v yarn 2> /dev/null) ]; then \
|
||||
@echo "yarn is not installed https://yarnpkg.com"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
@@ -22,7 +19,6 @@ OVERRIDE_ASSETS = $(shell find assets/override -type d 2> /dev/null) $(shell fin
|
||||
@touch $@
|
||||
|
||||
.podinstall:
|
||||
ifeq ($(OS), Darwin)
|
||||
ifdef POD
|
||||
@echo Getting Cocoapods dependencies;
|
||||
@cd ios && pod install;
|
||||
@@ -30,10 +26,13 @@ else
|
||||
@echo "Cocoapods is not installed https://cocoapods.org/"
|
||||
@exit 1
|
||||
endif
|
||||
endif
|
||||
|
||||
@touch $@
|
||||
|
||||
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)
|
||||
dist/assets: $(BASE_ASSETS) $(OVERRIDE_ASSETS)
|
||||
|
||||
@mkdir -p dist
|
||||
|
||||
@if [ -e dist/assets ] ; then \
|
||||
@@ -43,14 +42,60 @@ dist/assets: $(BASE_ASSETS) $(OVERRIDE_ASSETS)
|
||||
@echo "Generating app assets"
|
||||
@node scripts/make-dist-assets.js
|
||||
|
||||
pre-run: | .yarninstall .podinstall dist/assets ## Installs dependencies and assets
|
||||
pre-run: | .yarninstall .podinstall dist/assets
|
||||
|
||||
check-style: .yarninstall ## Runs eslint
|
||||
run: run-ios
|
||||
|
||||
start: | pre-run start-packager
|
||||
|
||||
stop: stop-packager
|
||||
|
||||
check-device-ios:
|
||||
@if ! [ $(shell command -v xcodebuild) ]; then \
|
||||
@echo "xcode is not installed"; \
|
||||
@exit 1; \
|
||||
fi
|
||||
@if ! [ $(shell command -v watchman) ]; then \
|
||||
@echo "watchman is not installed"; \
|
||||
@exit 1; \
|
||||
fi
|
||||
|
||||
run-ios: | check-device-ios start-build-packager
|
||||
@echo Running iOS app in development
|
||||
@react-native run-ios --simulator="${SIMULATOR}"
|
||||
|
||||
check-device-android:
|
||||
@if ! [ $(ANDROID_HOME) ]; then \
|
||||
@echo "ANDROID_HOME is not set"; \
|
||||
@exit 1; \
|
||||
fi
|
||||
@if ! [ $(shell command -v adb 2> /dev/null) ]; then \
|
||||
@echo "adb is not installed"; \
|
||||
@exit 1; \
|
||||
fi
|
||||
ifneq ($(shell adb get-state),device)
|
||||
@echo "no android device or emulator is running"
|
||||
@exit 1;
|
||||
endif
|
||||
@if ! [ $(shell command -v watchman 2> /dev/null) ]; then \
|
||||
@echo "watchman is not installed"; \
|
||||
@exit 1; \
|
||||
fi
|
||||
|
||||
run-android: | check-device-android start-build-packager prepare-android-build
|
||||
@echo Running Android app in development
|
||||
@react-native run-android --no-packager
|
||||
|
||||
test: | pre-run check-style
|
||||
@yarn test
|
||||
|
||||
check-style: .yarninstall
|
||||
@echo Checking for style guide compliance
|
||||
@yarn run check
|
||||
@node_modules/.bin/eslint --ext \".js\" --ignore-pattern node_modules --quiet .
|
||||
|
||||
clean: ## Cleans dependencies, previous builds and temp files
|
||||
clean:
|
||||
@echo Cleaning started
|
||||
|
||||
@yarn cache clean
|
||||
@rm -rf node_modules
|
||||
@rm -f .yarninstall
|
||||
@@ -59,6 +104,7 @@ clean: ## Cleans dependencies, previous builds and temp files
|
||||
@rm -rf ios/build
|
||||
@rm -rf ios/Pods
|
||||
@rm -rf android/app/build
|
||||
|
||||
@echo Cleanup finished
|
||||
|
||||
post-install:
|
||||
@@ -75,151 +121,110 @@ post-install:
|
||||
@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
|
||||
@sed -i'' -e 's|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);|auto("auto", Configuration.ORIENTATION_UNDEFINED, ActivityInfo.SCREEN_ORIENTATION_FULL_USER);|g' node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/params/Orientation.java
|
||||
@sed -i'' -e "s|super.onBackPressed();|this.moveTaskToBack(true);|g" node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java
|
||||
@sed -i'' -e "s|var AndroidTextInput = requireNativeComponent('AndroidTextInput', null);|var AndroidTextInput = requireNativeComponent('CustomTextInput', null);|g" node_modules/react-native/Libraries/Components/TextInput/TextInput.js
|
||||
@sed -i'' -e 's^getItemLayout || index <= this._highestMeasuredFrameIndex,^!getItemLayout || index !== -1,^g' node_modules/react-native/Libraries/Lists/VirtualizedList.js
|
||||
@if [ $(shell grep "const Platform" node_modules/react-native/Libraries/Lists/VirtualizedList.js | grep -civ grep) -eq 0 ]; then \
|
||||
sed $ -i'' -e "s|const ReactNative = require('ReactNative');|const ReactNative = require('ReactNative');`echo $\\\\\\r;`const Platform = require('Platform');|g" node_modules/react-native/Libraries/Lists/VirtualizedList.js; \
|
||||
fi
|
||||
@sed -i'' -e 's|transform: \[{scaleY: -1}\],|...Platform.select({android: {transform: \[{perspective: 1}, {scaleY: -1}\]}, ios: {transform: \[{scaleY: -1}\]}}),|g' node_modules/react-native/Libraries/Lists/VirtualizedList.js
|
||||
@cd ./node_modules/react-native-svg/ios && rm -rf PerformanceBezier && git clone https://github.com/adamwulf/PerformanceBezier.git
|
||||
@cd ./node_modules/mattermost-redux && yarn run build
|
||||
|
||||
start: | pre-run ## Starts the React Native packager server
|
||||
start-packager:
|
||||
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
node ./node_modules/react-native/local-cli/cli.js start; \
|
||||
else \
|
||||
echo React Native packager server already running; \
|
||||
ps -e | grep -i "cli.js start" | grep -iv grep | awk '{print $$1}' > server.PID; \
|
||||
ps -e | grep -i "cli.js start" | grep -v grep | awk '{print $$1}' > server.PID; \
|
||||
fi
|
||||
|
||||
stop: ## Stops the React Native packager server
|
||||
start-build-packager:
|
||||
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
node ./node_modules/react-native/local-cli/cli.js start --reset-cache & echo $$! > server.PID; \
|
||||
else \
|
||||
echo React Native packager server already running; \
|
||||
ps -e | grep -i "cli.js start" | grep -v grep | awk '{print $$1}' > server.PID; \
|
||||
fi
|
||||
|
||||
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; \
|
||||
@if [ -e "server.PID" ] ; then \
|
||||
kill -9 `cat server.PID` && rm server.PID; \
|
||||
echo React Native packager server stopped; \
|
||||
else \
|
||||
echo No React Native packager server running; \
|
||||
fi
|
||||
|
||||
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-ios-target:
|
||||
ifeq ($(ios_target), )
|
||||
@echo No target set to build iOS app
|
||||
@echo "Try running make build-ios TARGET where TARGET is one of dev, beta or release"
|
||||
@exit 1
|
||||
endif
|
||||
ifneq ($(ios_target), $(filter $(ios_target),dev beta release))
|
||||
@echo Invalid target set to build iOS app
|
||||
@echo "Try running make build-ios TARGET where TARGET is one of dev, beta or release"
|
||||
@exit 1
|
||||
endif
|
||||
|
||||
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
|
||||
do-build-ios:
|
||||
@echo "Building ios $(ios_target) app"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane ios $(ios_target)
|
||||
|
||||
@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
|
||||
build-ios: | check-ios-target pre-run check-style start-build-packager do-build-ios stop-packager
|
||||
|
||||
check-android-target:
|
||||
ifeq ($(android_target), )
|
||||
@echo No target set to build Android app
|
||||
@echo "Try running make build-android TARGET where TARGET is one of dev, beta or release"
|
||||
@exit 1
|
||||
endif
|
||||
ifneq ($(android_target), $(filter $(android_target),dev alpha release))
|
||||
@echo Invalid target set to build Android app
|
||||
@echo "Try running make build-android TARGET where TARGET is one of dev, beta or release"
|
||||
@exit 1
|
||||
endif
|
||||
|
||||
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
|
||||
do-build-android:
|
||||
@echo "Building android $(android_target) app"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane android $(android_target)
|
||||
|
||||
run-ios: | check-device-ios pre-run ## Runs the app on an iOS simulator
|
||||
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
node ./node_modules/react-native/local-cli/cli.js start & echo Running iOS app in development; \
|
||||
react-native run-ios --simulator="${SIMULATOR}"; \
|
||||
wait; \
|
||||
else \
|
||||
echo Running iOS app in development; \
|
||||
react-native run-ios --simulator="${SIMULATOR}"; \
|
||||
fi
|
||||
build-android: | check-android-target pre-run check-style start-build-packager prepare-android-build do-build-android stop-packager
|
||||
|
||||
run-android: | check-device-android pre-run prepare-android-build ## Runs the app on an Android emulator or dev device
|
||||
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
node ./node_modules/react-native/local-cli/cli.js 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-ios: | pre-run check-style ## Creates an iOS build
|
||||
ifneq ($(IOS_APP_GROUP),)
|
||||
@mkdir -p assets/override
|
||||
@echo "{\n\t\"AppGroupId\": \"$$IOS_APP_GROUP\"\n}" > assets/override/config.json
|
||||
endif
|
||||
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
node ./node_modules/react-native/local-cli/cli.js start & echo; \
|
||||
fi
|
||||
@echo "Building iOS app"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane ios build
|
||||
@ps -e | grep -i "cli.js start" | grep -iv grep | awk '{print $$1}' | xargs kill -9
|
||||
@rm -rf assets/override
|
||||
|
||||
build-android: | pre-run check-style prepare-android-build ## Creates an Android build
|
||||
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
node ./node_modules/react-native/local-cli/cli.js start & echo; \
|
||||
fi
|
||||
@echo "Building Android app"
|
||||
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane android build
|
||||
@ps -e | grep -i "cli.js start" | grep -iv grep | awk '{print $$1}' | xargs kill -9
|
||||
|
||||
unsigned-ios: pre-run check-style
|
||||
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
node ./node_modules/react-native/local-cli/cli.js start & echo; \
|
||||
fi
|
||||
do-unsigned-ios:
|
||||
@echo "Building unsigned iOS app"
|
||||
ifneq ($(IOS_APP_GROUP),)
|
||||
@mkdir -p assets/override
|
||||
@echo "{\n\t\"AppGroupId\": \"$$IOS_APP_GROUP\"\n}" > assets/override/config.json
|
||||
endif
|
||||
@cd fastlane && NODE_ENV=production bundle exec fastlane ios unsigned
|
||||
@mkdir -p build-ios
|
||||
@cd ios/ && xcodebuild -workspace Mattermost.xcworkspace/ -scheme Mattermost -sdk iphoneos -configuration Relase -parallelizeTargets -resultBundlePath ../build-ios/result -derivedDataPath ../build-ios/ CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
|
||||
@cd build-ios/ && mkdir -p Payload && cp -R Build/Products/Release-iphoneos/Mattermost.app Payload/ && zip -r Mattermost-unsigned.ipa Payload/
|
||||
@mv build-ios/Mattermost-unsigned.ipa .
|
||||
@rm -rf build-ios/
|
||||
@rm -rf assets/override
|
||||
@ps -e | grep -i "cli.js start" | grep -iv grep | awk '{print $$1}' | xargs kill -9
|
||||
|
||||
unsigned-android: pre-run check-style prepare-android-build
|
||||
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
|
||||
echo Starting React Native packager server; \
|
||||
node ./node_modules/react-native/local-cli/cli.js start & echo; \
|
||||
fi
|
||||
do-unsigned-android:
|
||||
@echo "Building unsigned Android app"
|
||||
@cd fastlane && NODE_ENV=production bundle exec fastlane android unsigned
|
||||
@mv android/app/build/outputs/apk/app-unsigned-unsigned.apk ./Mattermost-unsigned.apk
|
||||
@ps -e | grep -i "cli.js start" | grep -iv grep | awk '{print $$1}' | xargs kill -9
|
||||
|
||||
test: | pre-run check-style ## Runs tests
|
||||
@yarn test
|
||||
unsigned-android: pre-run check-style start-build-packager do-unsigned-android stop-packager
|
||||
|
||||
## 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}'
|
||||
unsigned-ios: pre-run check-style start-build-packager do-unsigned-ios stop-packager
|
||||
|
||||
alpha:
|
||||
@:
|
||||
|
||||
dev:
|
||||
@:
|
||||
|
||||
beta:
|
||||
@:
|
||||
|
||||
release:
|
||||
@:
|
||||
|
||||
485
NOTICE.txt
485
NOTICE.txt
@@ -8,221 +8,6 @@ This document includes a list of open source components used in Mattermost Mobil
|
||||
|
||||
--------
|
||||
|
||||
## fusejs
|
||||
|
||||
This product contains 'fusejs', a Lightweight fuzzy-search, in JavaScript by Kirollos Risk.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/krisk/Fuse
|
||||
|
||||
* LICENSE:
|
||||
|
||||
Apache License
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017 Kirollos Risk
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
---
|
||||
|
||||
## react-native-tooltip
|
||||
|
||||
This product contains a modified portion of 'react-native-tooltip', A react-native component from displaying tooltip. Uses UIMenuController.
|
||||
@@ -2362,207 +2147,15 @@ This product contains a modified version of 'react-native-media-controls' This p
|
||||
|
||||
* LICENSE:
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
he MIT License (MIT)
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
Copyright (c) 2017 Charlie
|
||||
|
||||
1. Definitions.
|
||||
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:
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -2590,71 +2183,3 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
---
|
||||
|
||||
## react-native-permissions
|
||||
|
||||
Request user permissions from React Native, iOS + Android.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/yonahforst/react-native-permissions
|
||||
|
||||
* LICENSE:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Yonah Forst
|
||||
|
||||
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-tableview
|
||||
|
||||
Native iOS UITableView for React Native with JSON support.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/aksonov/react-native-tableview
|
||||
|
||||
* LICENSE:
|
||||
|
||||
Copyright (c) 2015, aksonov
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---
|
||||
|
||||
90
README.md
90
README.md
@@ -5,9 +5,9 @@
|
||||
**Supported iOS versions:** 9.3+
|
||||
**Supported Android versions:** 5.0+
|
||||
|
||||
Mattermost is an open source Slack-alternative used by thousands of companies around the world in 11 languages. Learn more at [https://about.mattermost.com](https://about.mattermost.com).
|
||||
Mattermost is an open source Slack-alternative used by thousands of companies around the world in 11 languages. Learn more at https://mattermost.com.
|
||||
|
||||
You can download our apps from the [App Store](https://about.mattermost.com/mattermost-ios-app/) or [Google Play Store](https://about.mattermost.com/mattermost-android-app/), or build them yourself.
|
||||
You can download our apps from the [App Store](https://about.mattermost.com/mattermost-ios-app/) or [Google Play Store](https://about.mattermost.com/mattermost-android-app/), or package them yourself.
|
||||
|
||||
We plan on releasing monthly updates with new features - check the [changelog](https://github.com/mattermost/mattermost-mobile/blob/master/CHANGELOG.md) for what features are currently supported!
|
||||
|
||||
@@ -36,7 +36,85 @@ To help with testing app updates before they're released, you can:
|
||||
3. Follow [these instructions](https://docs.mattermost.com/developer/mobile-developer-setup.html) to set up your developer environment
|
||||
4. Join the [Native Mobile Apps channel](https://pre-release.mattermost.com/core/channels/native-mobile-apps) on our team site to ask questions
|
||||
|
||||
# Installing Dependencies
|
||||
|
||||
Follow the [React Native Getting Started Guide](https://facebook.github.io/react-native/docs/getting-started.html) for detailed instructions on setting up your local machine for development.
|
||||
|
||||
# Detailed configuration:
|
||||
|
||||
## Mac
|
||||
|
||||
- General requirements
|
||||
|
||||
- XCode 8.3
|
||||
- Install required packages using homebrew:
|
||||
```bash
|
||||
$ brew install watchman
|
||||
$ brew install yarn
|
||||
```
|
||||
|
||||
- Clone repository and configure:
|
||||
```bash
|
||||
$ git clone git@github.com:mattermost/mattermost-mobile.git
|
||||
$ cd mattermost-mobile
|
||||
$ npm install
|
||||
$ npm install -g react-native-cli
|
||||
```
|
||||
|
||||
- Run application
|
||||
```bash
|
||||
$ make run
|
||||
```
|
||||
|
||||
- Stop the packager server
|
||||
```bash
|
||||
$ make stop
|
||||
```
|
||||
## Linux:
|
||||
|
||||
- General requiriments:
|
||||
|
||||
- JDK 7 or greater
|
||||
- Android SDK
|
||||
- Virtualbox
|
||||
- An Android emulator: Genymotion or Android emulator. If using genymotion ensure that it uses existing adb tools (Settings: "Use custom Android SDK Tools")
|
||||
- Install watchman (do this globally):
|
||||
```bash
|
||||
$ git clone https://github.com/facebook/watchman.git
|
||||
$ cd watchman
|
||||
$ git checkout master
|
||||
$ ./autogen.sh
|
||||
$ ./configure
|
||||
$ make
|
||||
$ sudo make install
|
||||
```
|
||||
Configure your kernel to accept a lot of file watches, using a command like:
|
||||
```bash
|
||||
$ sudo sysctl -w fs.inotify.max_user_watches=1048576
|
||||
```
|
||||
|
||||
- Clone repository and configure:
|
||||
```bash
|
||||
$ git clone git@github.com:mattermost/mattermost-mobile.git
|
||||
$ cd mattermost-mobile
|
||||
$ npm install
|
||||
$ npm install -g react-native-cli
|
||||
```
|
||||
|
||||
- You can create a file named `assets/override/config.json` and add the url to the Mattermost server that you will use to develop:
|
||||
`{
|
||||
"DefaultServerUrl": "https://pre-release.mattermost.com"
|
||||
}`
|
||||
|
||||
To use a local Mattermost server you will need to configure the "DefaultServerUrl" depending on the emulator you will use:
|
||||
* IOs: "DefaultServerUrl": "http://localhost:8065"
|
||||
* Android: "DefaultServerUrl": "http://10.0.2.2:3000"
|
||||
* Genymotion: "DefaultServerUrl": "http://10.0.3.2:8065"
|
||||
|
||||
- Run application
|
||||
- Start emulator
|
||||
- Start react packager: `$ react-native start`
|
||||
- Run in emulator: `$ react-native run-android`
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
@@ -71,3 +149,11 @@ If your app is working properly, you should see a grey “Connecting…” bar t
|
||||
If you are seeing this message all the time, and your internet connection seems fine:
|
||||
|
||||
Ask your server administrator if the server uses NGINX or another webserver as a reverse proxy. If so, they should check that it is configured correctly for [supporting the websocket connection for APIv4 endpoints](https://docs.mattermost.com/install/install-ubuntu-1604.html#configuring-nginx-as-a-proxy-for-mattermost-server).
|
||||
|
||||
# Issues building app for own device using make build-*
|
||||
|
||||
That command is an internal pipeline command for mattermost mobile to publish the mobile apps to ````Apple App Store```` and ````Google Play Store````. All ````make build-*```` commands should be avoided for this reason.
|
||||
|
||||
To build the modified react native client use the instructions for [Running on Device](http://facebook.github.io/react-native/docs/running-on-device.html) from the [React Native Guide](https://facebook.github.io/react-native/docs/getting-started.html).
|
||||
|
||||
|
||||
|
||||
@@ -80,11 +80,6 @@ apply from: "../../node_modules/react-native/react.gradle"
|
||||
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
|
||||
|
||||
if (System.getenv("SENTRY_ENABLED") == "true") {
|
||||
project.ext.sentryCli = [
|
||||
logLevel: "debug",
|
||||
flavorAware: true
|
||||
]
|
||||
|
||||
apply from: "../../node_modules/react-native-sentry/sentry.gradle"
|
||||
}
|
||||
|
||||
@@ -111,8 +106,8 @@ android {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 23
|
||||
versionCode 92
|
||||
versionName "1.7.1"
|
||||
versionCode 77
|
||||
versionName "1.5.2"
|
||||
multiDexEnabled true
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
|
||||
@@ -56,21 +56,6 @@
|
||||
<activity
|
||||
android:name="com.reactnativenavigation.controllers.NavigationActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"/>
|
||||
<activity
|
||||
android:noHistory="false"
|
||||
android:name="com.mattermost.share.ShareActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/AppTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
// for sharing
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.mattermost.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.InputType;
|
||||
|
||||
import com.facebook.react.views.textinput.ReactEditText;
|
||||
|
||||
public class CustomTextInput extends ReactEditText {
|
||||
private boolean autoScroll = false;
|
||||
|
||||
public CustomTextInput(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
private boolean isMultiline() {
|
||||
return (getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLayoutRequested() {
|
||||
if (isMultiline() && !autoScroll) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setAutoScroll(boolean autoScroll) {
|
||||
this.autoScroll = autoScroll;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.mattermost.components;
|
||||
|
||||
import android.text.InputType;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import com.facebook.react.views.textinput.ReactTextInputManager;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewDefaults;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
public class CustomTextInputManager extends ReactTextInputManager {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "CustomTextInput";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomTextInput createViewInstance(ThemedReactContext context) {
|
||||
CustomTextInput editText = new CustomTextInput(context);
|
||||
int inputType = editText.getInputType();
|
||||
editText.setInputType(inputType & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
|
||||
editText.setReturnKeyType("done");
|
||||
editText.setTextSize(
|
||||
TypedValue.COMPLEX_UNIT_PX,
|
||||
(int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP)));
|
||||
return editText;
|
||||
}
|
||||
|
||||
@ReactProp(name = "autoScroll", defaultBoolean = false)
|
||||
public void setAutoScroll(CustomTextInput view, boolean autoScroll) {
|
||||
view.setAutoScroll(autoScroll);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,10 @@
|
||||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.reactnativenavigation.controllers.SplashActivity;
|
||||
|
||||
public class MainActivity extends SplashActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
/**
|
||||
* Reference: https://stackoverflow.com/questions/7944338/resume-last-activity-when-launcher-icon-is-clicked
|
||||
* 1. Open app from launcher/appDrawer
|
||||
* 2. Go home
|
||||
* 3. Send notification and open
|
||||
* 4. It creates a new Activity and Destroys the old
|
||||
* 5. Causing an unnecessary app restart
|
||||
* 6. This solution short-circuits the restart
|
||||
*/
|
||||
if (!isTaskRoot()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSplashLayout() {
|
||||
return R.layout.launch_screen;
|
||||
}
|
||||
@Override
|
||||
public int getSplashLayout() {
|
||||
return R.layout.launch_screen;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.mattermost.rnbeta;
|
||||
|
||||
import com.mattermost.share.SharePackage;
|
||||
import android.app.Application;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
@@ -71,8 +70,7 @@ public class MainApplication extends NavigationApplication implements INotificat
|
||||
new ReactNativeExceptionHandlerPackage(),
|
||||
new ReactNativeYouTube(),
|
||||
new ReactVideoPackage(),
|
||||
new RNReactNativeDocViewerPackage(),
|
||||
new SharePackage()
|
||||
new RNReactNativeDocViewerPackage()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,13 +91,6 @@ public class MainApplication extends NavigationApplication implements INotificat
|
||||
SoLoader.init(this, /* native exopackage */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clearHostOnActivityDestroy() {
|
||||
// This solves the issue where the splash screen does not go away
|
||||
// after the app is killed by the OS cause of memory or a long time in the background
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPushNotification getPushNotification(Context context, Bundle bundle, AppLifecycleFacade defaultFacade, AppLaunchHelper defaultAppLaunchHelper) {
|
||||
return new CustomPushNotification(
|
||||
|
||||
@@ -10,6 +10,8 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
|
||||
import com.mattermost.components.CustomTextInputManager;
|
||||
|
||||
public class MattermostPackage implements ReactPackage {
|
||||
private final MainApplication mApplication;
|
||||
|
||||
@@ -27,6 +29,8 @@ public class MattermostPackage implements ReactPackage {
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Arrays.<ViewManager>asList();
|
||||
return Arrays.<ViewManager>asList(
|
||||
new CustomTextInputManager()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +39,12 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A
|
||||
|
||||
private final BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() {
|
||||
@Override public void onReceive(Context context, Intent intent) {
|
||||
if (context != null) {
|
||||
|
||||
if (mVisibleActivity != null) {
|
||||
// Get the current configuration bundle
|
||||
RestrictionsManager myRestrictionsMgr =
|
||||
(RestrictionsManager) context
|
||||
.getSystemService(Context.RESTRICTIONS_SERVICE);
|
||||
(RestrictionsManager) mVisibleActivity
|
||||
.getSystemService(Context.RESTRICTIONS_SERVICE);
|
||||
managedConfig = myRestrictionsMgr.getApplicationRestrictions();
|
||||
|
||||
// Check current configuration settings, change your app's UI and
|
||||
@@ -65,11 +66,11 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A
|
||||
@Override
|
||||
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
|
||||
MattermostManagedModule managedModule = MattermostManagedModule.getInstance();
|
||||
if (managedModule != null && managedModule.isBlurAppScreenEnabled() && activity != null) {
|
||||
if (managedModule != null && managedModule.isBlurAppScreenEnabled()) {
|
||||
activity.getWindow().setFlags(LayoutParams.FLAG_SECURE,
|
||||
LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
if (managedConfig!= null && managedConfig.size() > 0 && activity != null) {
|
||||
if (managedConfig!= null && managedConfig.size() > 0) {
|
||||
activity.registerReceiver(restrictionsReceiver, restrictionsFilter);
|
||||
}
|
||||
}
|
||||
@@ -78,11 +79,9 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A
|
||||
public void onActivityResumed(Activity activity) {
|
||||
switchToVisible(activity);
|
||||
|
||||
ReactContext ctx = getRunningReactContext();
|
||||
if (managedConfig != null && managedConfig.size() > 0 && ctx != null) {
|
||||
|
||||
if (managedConfig != null && managedConfig.size() > 0) {
|
||||
RestrictionsManager myRestrictionsMgr =
|
||||
(RestrictionsManager) ctx
|
||||
(RestrictionsManager) activity
|
||||
.getSystemService(Context.RESTRICTIONS_SERVICE);
|
||||
|
||||
Bundle newConfig = myRestrictionsMgr.getApplicationRestrictions();
|
||||
@@ -175,15 +174,13 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void LoadManagedConfig(ReactContext ctx) {
|
||||
if (ctx != null) {
|
||||
RestrictionsManager myRestrictionsMgr =
|
||||
(RestrictionsManager) ctx
|
||||
.getSystemService(Context.RESTRICTIONS_SERVICE);
|
||||
public synchronized void LoadManagedConfig(Activity activity) {
|
||||
RestrictionsManager myRestrictionsMgr =
|
||||
(RestrictionsManager) activity
|
||||
.getSystemService(Context.RESTRICTIONS_SERVICE);
|
||||
|
||||
managedConfig = myRestrictionsMgr.getApplicationRestrictions();
|
||||
myRestrictionsMgr = null;
|
||||
}
|
||||
managedConfig = myRestrictionsMgr.getApplicationRestrictions();
|
||||
myRestrictionsMgr = null;
|
||||
}
|
||||
|
||||
public synchronized Bundle getManagedConfig() {
|
||||
@@ -191,10 +188,8 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A
|
||||
return managedConfig;
|
||||
}
|
||||
|
||||
ReactContext ctx = getRunningReactContext();
|
||||
|
||||
if (ctx != null) {
|
||||
LoadManagedConfig(ctx);
|
||||
if (mVisibleActivity != null) {
|
||||
LoadManagedConfig(mVisibleActivity);
|
||||
return managedConfig;
|
||||
}
|
||||
|
||||
@@ -203,11 +198,9 @@ public class NotificationsLifecycleFacade extends ActivityCallbacks implements A
|
||||
|
||||
public void sendConfigChanged(Bundle config) {
|
||||
Object result = Arguments.fromBundle(config);
|
||||
ReactContext ctx = getRunningReactContext();
|
||||
if (ctx != null) {
|
||||
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).
|
||||
emit("managedConfigDidChange", result);
|
||||
}
|
||||
getRunningReactContext().
|
||||
getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).
|
||||
emit("managedConfigDidChange", result);
|
||||
}
|
||||
|
||||
private boolean equalBundles(Bundle one, Bundle two) {
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
package com.mattermost.share;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.content.ContentUris;
|
||||
import android.os.Environment;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import java.io.*;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
// Class based on the steveevers DocumentHelper https://gist.github.com/steveevers/a5af24c226f44bb8fdc3
|
||||
|
||||
public class RealPathUtil {
|
||||
public static String getRealPathFromURI(final Context context, final Uri uri) {
|
||||
|
||||
final boolean isKitKatOrNewer = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||
|
||||
// DocumentProvider
|
||||
if (isKitKatOrNewer && DocumentsContract.isDocumentUri(context, uri)) {
|
||||
// ExternalStorageProvider
|
||||
if (isExternalStorageDocument(uri)) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
if ("primary".equalsIgnoreCase(type)) {
|
||||
return Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
}
|
||||
} else if (isDownloadsDocument(uri)) {
|
||||
// DownloadsProvider
|
||||
|
||||
final String id = DocumentsContract.getDocumentId(uri);
|
||||
final Uri contentUri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
||||
|
||||
return getDataColumn(context, contentUri, null, null);
|
||||
} else if (isMediaDocument(uri)) {
|
||||
// MediaProvider
|
||||
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
|
||||
Uri contentUri = null;
|
||||
if ("image".equals(type)) {
|
||||
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("video".equals(type)) {
|
||||
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("audio".equals(type)) {
|
||||
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
final String selection = "_id=?";
|
||||
final String[] selectionArgs = new String[] {
|
||||
split[1]
|
||||
};
|
||||
|
||||
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||
}
|
||||
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
// MediaStore (and general)
|
||||
|
||||
if (isGooglePhotosUri(uri)) {
|
||||
return uri.getLastPathSegment();
|
||||
}
|
||||
|
||||
String path = getDataColumn(context, uri, null, null);
|
||||
|
||||
if (path != null) {
|
||||
return path;
|
||||
}
|
||||
|
||||
// Try save to tmp file, and return tmp file path
|
||||
return getPathFromSavingTempFile(context, uri);
|
||||
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
return uri.getPath();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getPathFromSavingTempFile(Context context, final Uri uri) {
|
||||
File tmpFile;
|
||||
try {
|
||||
String fileName = uri.getLastPathSegment();
|
||||
tmpFile = File.createTempFile("tmp", fileName, context.getCacheDir());
|
||||
|
||||
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
|
||||
|
||||
FileChannel src = new FileInputStream(pfd.getFileDescriptor()).getChannel();
|
||||
FileChannel dst = new FileOutputStream(tmpFile).getChannel();
|
||||
dst.transferFrom(src, 0, src.size());
|
||||
src.close();
|
||||
dst.close();
|
||||
} catch (IOException ex) {
|
||||
return null;
|
||||
}
|
||||
return tmpFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
public static String getDataColumn(Context context, Uri uri, String selection,
|
||||
String[] selectionArgs) {
|
||||
|
||||
Cursor cursor = null;
|
||||
final String column = "_data";
|
||||
final String[] projection = {
|
||||
column
|
||||
};
|
||||
|
||||
try {
|
||||
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
|
||||
null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
final int index = cursor.getColumnIndexOrThrow(column);
|
||||
return cursor.getString(index);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isExternalStorageDocument(Uri uri) {
|
||||
return "com.android.externalstorage.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
public static boolean isDownloadsDocument(Uri uri) {
|
||||
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
public static boolean isMediaDocument(Uri uri) {
|
||||
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
public static boolean isGooglePhotosUri(Uri uri) {
|
||||
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
public static String getExtension(String uri) {
|
||||
if (uri == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int dot = uri.lastIndexOf(".");
|
||||
if (dot >= 0) {
|
||||
return uri.substring(dot);
|
||||
} else {
|
||||
// No extension.
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static String getMimeType(File file) {
|
||||
|
||||
String extension = getExtension(file.getName());
|
||||
|
||||
if (extension.length() > 0)
|
||||
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
public static String getMimeType(String filePath) {
|
||||
File file = new File(filePath);
|
||||
return getMimeType(file);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.mattermost.share;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.ReactActivity;
|
||||
|
||||
public class ShareActivity extends ReactActivity {
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
return "MattermostShare";
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
package com.mattermost.share;
|
||||
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import java.io.InputStream;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.IOException;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class ShareModule extends ReactContextBaseJavaModule {
|
||||
private final OkHttpClient client = new OkHttpClient();
|
||||
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
public ShareModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "MattermostShare";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void clear() {
|
||||
Activity currentActivity = getCurrentActivity();
|
||||
|
||||
if (currentActivity != null) {
|
||||
Intent intent = currentActivity.getIntent();
|
||||
intent.setAction("");
|
||||
intent.removeExtra(Intent.EXTRA_TEXT);
|
||||
intent.removeExtra(Intent.EXTRA_STREAM);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void close(ReadableMap data) {
|
||||
getCurrentActivity().finish();
|
||||
|
||||
if (data != null) {
|
||||
ReadableArray files = data.getArray("files");
|
||||
String serverUrl = data.getString("url");
|
||||
String token = data.getString("token");
|
||||
JSONObject postData = buildPostObject(data);
|
||||
|
||||
if (files.size() > 0) {
|
||||
uploadFiles(serverUrl, token, files, postData);
|
||||
} else {
|
||||
try {
|
||||
post(serverUrl, token, postData);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void data(Promise promise) {
|
||||
promise.resolve(processIntent());
|
||||
}
|
||||
|
||||
public WritableArray processIntent() {
|
||||
WritableMap map = Arguments.createMap();
|
||||
WritableArray items = Arguments.createArray();
|
||||
|
||||
String text = "";
|
||||
String type = "";
|
||||
String action = "";
|
||||
|
||||
Activity currentActivity = getCurrentActivity();
|
||||
|
||||
if (currentActivity != null) {
|
||||
Intent intent = currentActivity.getIntent();
|
||||
action = intent.getAction();
|
||||
type = intent.getType();
|
||||
if (type == null) {
|
||||
type = "";
|
||||
}
|
||||
|
||||
if (Intent.ACTION_SEND.equals(action) && "text/plain".equals(type)) {
|
||||
text = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
map.putString("value", text);
|
||||
map.putString("type", type);
|
||||
items.pushMap(map);
|
||||
} else if (Intent.ACTION_SEND.equals(action)) {
|
||||
Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
text = "file://" + RealPathUtil.getRealPathFromURI(currentActivity, uri);
|
||||
map.putString("value", text);
|
||||
map.putString("type", type);
|
||||
items.pushMap(map);
|
||||
} else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
|
||||
ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
for (Uri uri : uris) {
|
||||
String filePath = RealPathUtil.getRealPathFromURI(currentActivity, uri);
|
||||
map = Arguments.createMap();
|
||||
text = "file://" + filePath;
|
||||
map.putString("value", text);
|
||||
map.putString("type", RealPathUtil.getMimeType(filePath));
|
||||
items.pushMap(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private JSONObject buildPostObject(ReadableMap data) {
|
||||
JSONObject json = new JSONObject();
|
||||
try {
|
||||
json.put("user_id", data.getString("currentUserId"));
|
||||
json.put("channel_id", data.getString("channelId"));
|
||||
json.put("message", data.getString("value"));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
private void post(String serverUrl, String token, JSONObject postData) throws IOException {
|
||||
RequestBody body = RequestBody.create(JSON, postData.toString());
|
||||
Request request = new Request.Builder()
|
||||
.header("Authorization", "BEARER " + token)
|
||||
.url(serverUrl + "/api/v4/posts")
|
||||
.post(body)
|
||||
.build();
|
||||
Response response = client.newCall(request).execute();
|
||||
}
|
||||
|
||||
private void uploadFiles(String serverUrl, String token, ReadableArray files, JSONObject postData) {
|
||||
try {
|
||||
MultipartBody.Builder builder = new MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM);
|
||||
|
||||
for(int i = 0 ; i < files.size() ; i++) {
|
||||
ReadableMap file = files.getMap(i);
|
||||
String filePath = file.getString("fullPath").replaceFirst("file://", "");
|
||||
File fileInfo = new File(filePath);
|
||||
if (fileInfo.exists()) {
|
||||
final MediaType MEDIA_TYPE = MediaType.parse(file.getString("mimeType"));
|
||||
builder.addFormDataPart("files", file.getString("filename"), RequestBody.create(MEDIA_TYPE, fileInfo));
|
||||
}
|
||||
}
|
||||
|
||||
builder.addFormDataPart("channel_id", postData.getString("channel_id"));
|
||||
RequestBody body = builder.build();
|
||||
Request request = new Request.Builder()
|
||||
.header("Authorization", "BEARER " + token)
|
||||
.url(serverUrl + "/api/v4/files")
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (response.isSuccessful()) {
|
||||
String responseData = response.body().string();
|
||||
JSONObject responseJson = new JSONObject(responseData);
|
||||
JSONArray fileInfoArray = responseJson.getJSONArray("file_infos");
|
||||
JSONArray file_ids = new JSONArray();
|
||||
for(int i = 0 ; i < fileInfoArray.length() ; i++) {
|
||||
JSONObject fileInfo = fileInfoArray.getJSONObject(i);
|
||||
file_ids.put(fileInfo.getString("id"));
|
||||
}
|
||||
postData.put("file_ids", file_ids);
|
||||
post(serverUrl, token, postData);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.mattermost.share;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.ReactPackage;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SharePackage implements ReactPackage {
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
return Arrays.<NativeModule>asList(new ShareModule(reactContext));
|
||||
}
|
||||
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ export function calculateDeviceDimensions() {
|
||||
type: DeviceTypes.DEVICE_DIMENSIONS_CHANGED,
|
||||
data: {
|
||||
deviceHeight: height,
|
||||
deviceWidth: width,
|
||||
},
|
||||
deviceWidth: width
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function connection(isOnline) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: DeviceTypes.CONNECTION_CHANGED,
|
||||
data: isOnline,
|
||||
data: isOnline
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
@@ -28,21 +28,21 @@ export function connection(isOnline) {
|
||||
export function setStatusBarHeight(height = 20) {
|
||||
return {
|
||||
type: DeviceTypes.STATUSBAR_HEIGHT_CHANGED,
|
||||
data: height,
|
||||
data: height
|
||||
};
|
||||
}
|
||||
|
||||
export function setDeviceOrientation(orientation) {
|
||||
return {
|
||||
type: DeviceTypes.DEVICE_ORIENTATION_CHANGED,
|
||||
data: orientation,
|
||||
data: orientation
|
||||
};
|
||||
}
|
||||
|
||||
export function setDeviceAsTablet() {
|
||||
return {
|
||||
type: DeviceTypes.DEVICE_TYPE_CHANGED,
|
||||
data: true,
|
||||
data: true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -51,5 +51,5 @@ export default {
|
||||
connection,
|
||||
setDeviceOrientation,
|
||||
setDeviceAsTablet,
|
||||
setStatusBarHeight,
|
||||
setStatusBarHeight
|
||||
};
|
||||
|
||||
@@ -11,15 +11,15 @@ export function handleUpdateUserNotifyProps(notifyProps) {
|
||||
const config = state.entities.general.config;
|
||||
const {currentUserId} = state.entities.users;
|
||||
|
||||
const {interval, user_id: userId, ...otherProps} = notifyProps;
|
||||
const {interval, user_id, ...otherProps} = notifyProps;
|
||||
|
||||
const email = notifyProps.email;
|
||||
if (config.EnableEmailBatching === 'true' && email !== 'false') {
|
||||
const emailInterval = [{
|
||||
user_id: userId,
|
||||
user_id,
|
||||
category: Preferences.CATEGORY_NOTIFICATIONS,
|
||||
name: Preferences.EMAIL_INTERVAL,
|
||||
value: interval,
|
||||
value: interval
|
||||
}];
|
||||
|
||||
savePreferences(currentUserId, emailInterval)(dispatch, getState);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
export function dismissBanner(text) {
|
||||
return {
|
||||
type: ViewTypes.ANNOUNCEMENT_BANNER,
|
||||
data: text,
|
||||
};
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
fetchMyChannelsAndMembers,
|
||||
markChannelAsRead,
|
||||
selectChannel,
|
||||
leaveChannel as serviceLeaveChannel,
|
||||
leaveChannel as serviceLeaveChannel
|
||||
} from 'mattermost-redux/actions/channels';
|
||||
import {getPosts, getPostsBefore, getPostsSince, getPostThread} from 'mattermost-redux/actions/posts';
|
||||
import {getFilesForPost} from 'mattermost-redux/actions/files';
|
||||
@@ -19,14 +19,13 @@ import {getTeamMembersByIds} from 'mattermost-redux/actions/teams';
|
||||
import {getProfilesInChannel} from 'mattermost-redux/actions/users';
|
||||
import {General, Preferences} from 'mattermost-redux/constants';
|
||||
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getTeamByName} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {
|
||||
getChannelByName,
|
||||
getDirectChannelName,
|
||||
getUserIdFromChannelName,
|
||||
isDirectChannel,
|
||||
isGroupChannel,
|
||||
isGroupChannel
|
||||
} from 'mattermost-redux/utils/channel_utils';
|
||||
import {getLastCreateAt} from 'mattermost-redux/utils/post_utils';
|
||||
import {getPreferencesByCategory} from 'mattermost-redux/utils/preference_utils';
|
||||
@@ -41,18 +40,6 @@ export function loadChannelsIfNecessary(teamId) {
|
||||
};
|
||||
}
|
||||
|
||||
export function loadChannelsByTeamName(teamName) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {currentTeamId} = state.entities.teams;
|
||||
const team = getTeamByName(state, teamName);
|
||||
|
||||
if (team && team.id !== currentTeamId) {
|
||||
await dispatch(fetchMyChannelsAndMembers(team.id));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function loadProfilesAndTeamMembersForDMSidebar(teamId) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
@@ -71,7 +58,7 @@ export function loadProfilesAndTeamMembersForDMSidebar(teamId) {
|
||||
user_id: currentUserId,
|
||||
category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||
name,
|
||||
value: 'true',
|
||||
value: 'true'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -143,7 +130,7 @@ export function loadProfilesAndTeamMembersForDMSidebar(teamId) {
|
||||
actions.push({
|
||||
type: UserTypes.RECEIVED_PROFILE_IN_CHANNEL,
|
||||
data: {user_id: members[i]},
|
||||
id: channel.id,
|
||||
id: channel.id
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -162,15 +149,10 @@ export function loadPostsIfNecessaryWithRetry(channelId) {
|
||||
|
||||
const time = Date.now();
|
||||
|
||||
let loadMorePostsVisible = true;
|
||||
let received;
|
||||
if (!postsIds || postsIds.length < ViewTypes.POST_VISIBILITY_CHUNK_SIZE) {
|
||||
// Get the first page of posts if it appears we haven't gotten it yet, like the webapp
|
||||
received = await retryGetPostsAction(getPosts(channelId), dispatch, getState);
|
||||
|
||||
if (received) {
|
||||
loadMorePostsVisible = received.order.length >= ViewTypes.POST_VISIBILITY_CHUNK_SIZE;
|
||||
}
|
||||
} else {
|
||||
const {lastConnectAt} = state.device.websocket;
|
||||
const lastGetPosts = state.views.channel.lastGetPosts[channelId];
|
||||
@@ -187,21 +169,15 @@ export function loadPostsIfNecessaryWithRetry(channelId) {
|
||||
}
|
||||
|
||||
received = await retryGetPostsAction(getPostsSince(channelId, since), dispatch, getState);
|
||||
|
||||
if (received) {
|
||||
loadMorePostsVisible = postsIds.length + received.order.length >= ViewTypes.POST_VISIBILITY_CHUNK_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
if (received) {
|
||||
dispatch({
|
||||
type: ViewTypes.RECEIVED_POSTS_FOR_CHANNEL_AT_TIME,
|
||||
channelId,
|
||||
time,
|
||||
time
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(setLoadMorePostsVisible(loadMorePostsVisible));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -290,23 +266,20 @@ export function handleSelectChannel(channelId) {
|
||||
return async (dispatch, getState) => {
|
||||
const {currentTeamId} = getState().entities.teams;
|
||||
|
||||
dispatch(setLoadMorePostsVisible(true));
|
||||
|
||||
loadPostsIfNecessaryWithRetry(channelId)(dispatch, getState);
|
||||
selectChannel(channelId)(dispatch, getState);
|
||||
|
||||
dispatch(batchActions([
|
||||
{
|
||||
type: ViewTypes.SET_INITIAL_POST_VISIBILITY,
|
||||
data: channelId,
|
||||
data: channelId
|
||||
},
|
||||
setChannelLoading(false),
|
||||
{
|
||||
type: ViewTypes.SET_LAST_CHANNEL_FOR_TEAM,
|
||||
teamId: currentTeamId,
|
||||
channelId,
|
||||
},
|
||||
]));
|
||||
channelId
|
||||
}
|
||||
]), 'BATCH_CHANNEL_LOADED');
|
||||
};
|
||||
}
|
||||
|
||||
@@ -315,7 +288,7 @@ export function handlePostDraftChanged(channelId, draft) {
|
||||
dispatch({
|
||||
type: ViewTypes.POST_DRAFT_CHANGED,
|
||||
channelId,
|
||||
draft,
|
||||
draft
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
@@ -324,7 +297,7 @@ export function handlePostDraftSelectionChanged(channelId, cursorPosition) {
|
||||
return {
|
||||
type: ViewTypes.POST_DRAFT_SELECTION_CHANGED,
|
||||
channelId,
|
||||
cursorPosition,
|
||||
cursorPosition
|
||||
};
|
||||
}
|
||||
|
||||
@@ -343,7 +316,7 @@ export function insertToDraft(value) {
|
||||
cursorPosition = threadDraft.cursorPosition;
|
||||
action = {
|
||||
type: ViewTypes.COMMENT_DRAFT_CHANGED,
|
||||
rootId: threadId,
|
||||
rootId: threadId
|
||||
};
|
||||
} else if (state.views.channel.drafts[channelId]) {
|
||||
const channelDraft = state.views.channel.drafts[channelId];
|
||||
@@ -351,7 +324,7 @@ export function insertToDraft(value) {
|
||||
cursorPosition = channelDraft.cursorPosition;
|
||||
action = {
|
||||
type: ViewTypes.POST_DRAFT_CHANGED,
|
||||
channelId,
|
||||
channelId
|
||||
};
|
||||
}
|
||||
|
||||
@@ -365,7 +338,7 @@ export function insertToDraft(value) {
|
||||
if (action && nextDraft !== draft) {
|
||||
dispatch({
|
||||
...action,
|
||||
draft: nextDraft,
|
||||
draft: nextDraft
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -380,7 +353,7 @@ export function toggleDMChannel(otherUserId, visible) {
|
||||
user_id: currentUserId,
|
||||
category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW,
|
||||
name: otherUserId,
|
||||
value: visible,
|
||||
value: visible
|
||||
}];
|
||||
|
||||
savePreferences(currentUserId, dm)(dispatch, getState);
|
||||
@@ -396,7 +369,7 @@ export function toggleGMChannel(channelId, visible) {
|
||||
user_id: currentUserId,
|
||||
category: Preferences.CATEGORY_GROUP_CHANNEL_SHOW,
|
||||
name: channelId,
|
||||
value: visible,
|
||||
value: visible
|
||||
}];
|
||||
|
||||
savePreferences(currentUserId, gm)(dispatch, getState);
|
||||
@@ -449,28 +422,28 @@ export function leaveChannel(channel, reset = false) {
|
||||
export function setChannelLoading(loading = true) {
|
||||
return {
|
||||
type: ViewTypes.SET_CHANNEL_LOADER,
|
||||
loading,
|
||||
loading
|
||||
};
|
||||
}
|
||||
|
||||
export function setChannelRefreshing(loading = true) {
|
||||
return {
|
||||
type: ViewTypes.SET_CHANNEL_REFRESHING,
|
||||
loading,
|
||||
loading
|
||||
};
|
||||
}
|
||||
|
||||
export function setChannelRetryFailed(failed = true) {
|
||||
return {
|
||||
type: ViewTypes.SET_CHANNEL_RETRY_FAILED,
|
||||
failed,
|
||||
failed
|
||||
};
|
||||
}
|
||||
|
||||
export function setChannelDisplayName(displayName) {
|
||||
return {
|
||||
type: ViewTypes.SET_CHANNEL_DISPLAY_NAME,
|
||||
displayName,
|
||||
displayName
|
||||
};
|
||||
}
|
||||
|
||||
@@ -487,16 +460,16 @@ export function increasePostVisibility(channelId, focusedPostId) {
|
||||
|
||||
// Check if we already have the posts that we want to show
|
||||
if (!focusedPostId) {
|
||||
const postsInChannel = state.entities.posts.postsInChannel[channelId] || [];
|
||||
const loadedPostCount = postsInChannel.length;
|
||||
const loadedPostCount = state.entities.posts.postsInChannel[channelId].length;
|
||||
const desiredPostVisibility = currentPostVisibility + ViewTypes.POST_VISIBILITY_CHUNK_SIZE;
|
||||
|
||||
if (loadedPostCount >= desiredPostVisibility) {
|
||||
// We already have the posts, so we just need to show them
|
||||
dispatch(batchActions([
|
||||
doIncreasePostVisibility(channelId),
|
||||
setLoadMorePostsVisible(true),
|
||||
]));
|
||||
dispatch({
|
||||
type: ViewTypes.INCREASE_POST_VISIBILITY,
|
||||
data: channelId,
|
||||
amount: ViewTypes.POST_VISIBILITY_CHUNK_SIZE
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -505,49 +478,33 @@ export function increasePostVisibility(channelId, focusedPostId) {
|
||||
dispatch({
|
||||
type: ViewTypes.LOADING_POSTS,
|
||||
data: true,
|
||||
channelId,
|
||||
channelId
|
||||
});
|
||||
|
||||
const pageSize = ViewTypes.POST_VISIBILITY_CHUNK_SIZE;
|
||||
const page = Math.floor(currentPostVisibility / pageSize);
|
||||
const page = Math.floor(currentPostVisibility / ViewTypes.POST_VISIBILITY_CHUNK_SIZE);
|
||||
|
||||
let result;
|
||||
if (focusedPostId) {
|
||||
result = await getPostsBefore(channelId, focusedPostId, page, pageSize)(dispatch, getState);
|
||||
result = await getPostsBefore(channelId, focusedPostId, page, ViewTypes.POST_VISIBILITY_CHUNK_SIZE)(dispatch, getState);
|
||||
} else {
|
||||
result = await getPosts(channelId, page, pageSize)(dispatch, getState);
|
||||
result = await getPosts(channelId, page, ViewTypes.POST_VISIBILITY_CHUNK_SIZE)(dispatch, getState);
|
||||
}
|
||||
|
||||
const actions = [{
|
||||
type: ViewTypes.LOADING_POSTS,
|
||||
data: false,
|
||||
channelId,
|
||||
}];
|
||||
|
||||
const posts = result.data;
|
||||
if (posts) {
|
||||
// make sure to increment the posts visibility
|
||||
// only if we got results
|
||||
actions.push(doIncreasePostVisibility(channelId));
|
||||
|
||||
actions.push(setLoadMorePostsVisible(posts.order.length >= pageSize));
|
||||
dispatch({
|
||||
type: ViewTypes.INCREASE_POST_VISIBILITY,
|
||||
data: channelId,
|
||||
amount: ViewTypes.POST_VISIBILITY_CHUNK_SIZE
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(batchActions(actions));
|
||||
};
|
||||
}
|
||||
|
||||
function doIncreasePostVisibility(channelId) {
|
||||
return {
|
||||
type: ViewTypes.INCREASE_POST_VISIBILITY,
|
||||
data: channelId,
|
||||
amount: ViewTypes.POST_VISIBILITY_CHUNK_SIZE,
|
||||
};
|
||||
}
|
||||
|
||||
function setLoadMorePostsVisible(visible) {
|
||||
return {
|
||||
type: ViewTypes.SET_LOAD_MORE_POSTS_VISIBLE,
|
||||
data: visible,
|
||||
dispatch({
|
||||
type: ViewTypes.LOADING_POSTS,
|
||||
data: false,
|
||||
channelId
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ import {ViewTypes} from 'app/constants';
|
||||
|
||||
export function setLastUpgradeCheck() {
|
||||
return {
|
||||
type: ViewTypes.SET_LAST_UPGRADE_CHECK,
|
||||
type: ViewTypes.SET_LAST_UPGRADE_CHECK
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export function executeCommand(message, channelId, rootId) {
|
||||
channel_id: channelId,
|
||||
team_id: teamId,
|
||||
root_id: rootId,
|
||||
parent_id: rootId,
|
||||
parent_id: rootId
|
||||
};
|
||||
|
||||
let msg = message;
|
||||
|
||||
@@ -18,7 +18,7 @@ export function handleCreateChannel(displayName, purpose, header, type) {
|
||||
display_name: displayName,
|
||||
purpose,
|
||||
header,
|
||||
type,
|
||||
type
|
||||
};
|
||||
|
||||
const {data} = await createChannel(channel, currentUserId)(dispatch, getState);
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {uploadProfileImage, updateMe} from 'mattermost-redux/actions/users';
|
||||
import {buildFileUploadData} from 'app/utils/file';
|
||||
|
||||
export function updateUser(user, success, error) {
|
||||
return async (dispatch, getState) => {
|
||||
const result = await updateMe(user)(dispatch, getState);
|
||||
const {data, error: err} = result;
|
||||
if (data && success) {
|
||||
success(data);
|
||||
} else if (err && error) {
|
||||
error({id: err.server_error_id, ...err});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export function handleUploadProfileImage(image, userId) {
|
||||
return async (dispatch, getState) => {
|
||||
const imageData = buildFileUploadData(image);
|
||||
return await uploadProfileImage(userId, imageData)(dispatch, getState);
|
||||
};
|
||||
}
|
||||
@@ -29,16 +29,6 @@ export function addReactionToLatestPost(emoji, rootId) {
|
||||
export function addRecentEmoji(emoji) {
|
||||
return {
|
||||
type: ViewTypes.ADD_RECENT_EMOJI,
|
||||
emoji,
|
||||
};
|
||||
}
|
||||
|
||||
export function incrementEmojiPickerPage() {
|
||||
return async (dispatch) => {
|
||||
dispatch({
|
||||
type: ViewTypes.INCREMENT_EMOJI_PICKER_PAGE,
|
||||
});
|
||||
|
||||
return {data: true};
|
||||
emoji
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ import {ViewTypes} from 'app/constants';
|
||||
export function addFileToFetchCache(url) {
|
||||
return {
|
||||
type: ViewTypes.ADD_FILE_TO_FETCH_CACHE,
|
||||
url,
|
||||
url
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import FormData from 'form-data';
|
||||
import {Platform} from 'react-native';
|
||||
import {uploadFile} from 'mattermost-redux/actions/files';
|
||||
import {lookupMimeType, parseClientIdsFromFormData} from 'mattermost-redux/utils/file_utils';
|
||||
|
||||
import {
|
||||
buildFileUploadData,
|
||||
encodeHeaderURIStringToUTF8,
|
||||
generateId,
|
||||
} from 'app/utils/file';
|
||||
import {generateId} from 'app/utils/file';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
export function handleUploadFiles(files, rootId) {
|
||||
@@ -18,20 +16,41 @@ export function handleUploadFiles(files, rootId) {
|
||||
const channelId = state.entities.channels.currentChannelId;
|
||||
const formData = new FormData();
|
||||
const clientIds = [];
|
||||
const re = /heic/i;
|
||||
|
||||
files.forEach((file) => {
|
||||
const fileData = buildFileUploadData(file);
|
||||
let name = file.fileName || file.path || file.uri;
|
||||
|
||||
if (name.includes('/')) {
|
||||
name = name.split('/').pop();
|
||||
}
|
||||
|
||||
let mimeType = lookupMimeType(name);
|
||||
let extension = name.split('.').pop().replace('.', '');
|
||||
const uri = file.uri;
|
||||
const clientId = generateId();
|
||||
|
||||
if (re.test(extension)) {
|
||||
extension = 'JPG';
|
||||
name = name.replace(re, 'jpg');
|
||||
mimeType = 'image/jpeg';
|
||||
}
|
||||
|
||||
clientIds.push({
|
||||
clientId,
|
||||
localPath: fileData.uri,
|
||||
name: fileData.name,
|
||||
type: fileData.mimeType,
|
||||
extension: fileData.extension,
|
||||
localPath: uri,
|
||||
name,
|
||||
type: mimeType,
|
||||
extension
|
||||
});
|
||||
|
||||
fileData.name = encodeHeaderURIStringToUTF8(fileData.name);
|
||||
const fileData = {
|
||||
uri,
|
||||
name,
|
||||
type: mimeType,
|
||||
extension
|
||||
};
|
||||
|
||||
formData.append('files', fileData);
|
||||
formData.append('channel_id', channelId);
|
||||
formData.append('client_ids', clientId);
|
||||
@@ -46,11 +65,10 @@ export function handleUploadFiles(files, rootId) {
|
||||
type: ViewTypes.SET_TEMP_UPLOAD_FILES_FOR_POST_DRAFT,
|
||||
clientIds,
|
||||
channelId,
|
||||
rootId,
|
||||
rootId
|
||||
});
|
||||
|
||||
const clientIdsArray = clientIds.map((c) => c.clientId);
|
||||
await uploadFile(channelId, rootId, clientIdsArray, formData, formBoundary)(dispatch, getState);
|
||||
await uploadFile(channelId, rootId, parseClientIdsFromFormData(formData), formData, formBoundary)(dispatch, getState);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,11 +78,13 @@ export function retryFileUpload(file, rootId) {
|
||||
|
||||
const channelId = state.entities.channels.currentChannelId;
|
||||
const formData = new FormData();
|
||||
const fileData = buildFileUploadData(file);
|
||||
|
||||
fileData.uri = file.localPath;
|
||||
const fileData = {
|
||||
uri: file.localPath,
|
||||
name: file.name,
|
||||
type: file.type
|
||||
};
|
||||
|
||||
fileData.name = encodeHeaderURIStringToUTF8(fileData.name);
|
||||
formData.append('files', fileData);
|
||||
formData.append('channel_id', channelId);
|
||||
formData.append('client_ids', file.clientId);
|
||||
@@ -78,7 +98,7 @@ export function retryFileUpload(file, rootId) {
|
||||
type: ViewTypes.RETRY_UPLOAD_FILE_FOR_POST,
|
||||
clientId: file.clientId,
|
||||
channelId,
|
||||
rootId,
|
||||
rootId
|
||||
});
|
||||
|
||||
await uploadFile(channelId, rootId, [file.clientId], formData, formBoundary)(dispatch, getState);
|
||||
@@ -89,7 +109,7 @@ export function handleClearFiles(channelId, rootId) {
|
||||
return {
|
||||
type: ViewTypes.CLEAR_FILES_FOR_POST_DRAFT,
|
||||
channelId,
|
||||
rootId,
|
||||
rootId
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,7 +117,7 @@ export function handleClearFailedFiles(channelId, rootId) {
|
||||
return {
|
||||
type: ViewTypes.CLEAR_FAILED_FILES_FOR_POST_DRAFT,
|
||||
channelId,
|
||||
rootId,
|
||||
rootId
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,7 +126,7 @@ export function handleRemoveFile(clientId, channelId, rootId) {
|
||||
type: ViewTypes.REMOVE_FILE_FROM_POST_DRAFT,
|
||||
clientId,
|
||||
channelId,
|
||||
rootId,
|
||||
rootId
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,6 +134,6 @@ export function handleRemoveLastFile(channelId, rootId) {
|
||||
return {
|
||||
type: ViewTypes.REMOVE_LAST_FILE_FROM_POST_DRAFT,
|
||||
channelId,
|
||||
rootId,
|
||||
rootId
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {getDataRetentionPolicy} from 'mattermost-redux/actions/general';
|
||||
import {GeneralTypes} from 'mattermost-redux/action_types';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {Client, Client4} from 'mattermost-redux/client';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
@@ -11,7 +9,7 @@ export function handleLoginIdChanged(loginId) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ViewTypes.LOGIN_ID_CHANGED,
|
||||
loginId,
|
||||
loginId
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
@@ -20,31 +18,25 @@ export function handlePasswordChanged(password) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ViewTypes.PASSWORD_CHANGED,
|
||||
password,
|
||||
password
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function handleSuccessfulLogin() {
|
||||
return async (dispatch, getState) => {
|
||||
const {config, license} = getState().entities.general;
|
||||
const token = Client4.getToken();
|
||||
const url = Client4.getUrl();
|
||||
|
||||
dispatch({
|
||||
type: GeneralTypes.RECEIVED_APP_CREDENTIALS,
|
||||
data: {
|
||||
url,
|
||||
token,
|
||||
},
|
||||
token
|
||||
}
|
||||
}, getState);
|
||||
|
||||
if (config.DataRetentionEnableMessageDeletion && config.DataRetentionEnableMessageDeletion === 'true' &&
|
||||
license.IsLicensed === 'true' && license.DataRetention === 'true') {
|
||||
getDataRetentionPolicy()(dispatch, getState);
|
||||
} else {
|
||||
dispatch({type: GeneralTypes.RECEIVED_DATA_RETENTION_POLICY, data: {}});
|
||||
}
|
||||
Client.setToken(token);
|
||||
Client.setUrl(url);
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -73,5 +65,5 @@ export default {
|
||||
handleLoginIdChanged,
|
||||
handlePasswordChanged,
|
||||
handleSuccessfulLogin,
|
||||
getSession,
|
||||
getSession
|
||||
};
|
||||
|
||||
@@ -14,27 +14,24 @@ import {recordTime} from 'app/utils/segment';
|
||||
import {
|
||||
handleSelectChannel,
|
||||
setChannelDisplayName,
|
||||
retryGetPostsAction,
|
||||
retryGetPostsAction
|
||||
} from 'app/actions/views/channel';
|
||||
|
||||
export function loadConfigAndLicense() {
|
||||
return async (dispatch, getState) => {
|
||||
const {currentUserId} = getState().entities.users;
|
||||
const [configData, licenseData] = await Promise.all([
|
||||
getClientConfig()(dispatch, getState),
|
||||
getLicenseConfig()(dispatch, getState),
|
||||
getLicenseConfig()(dispatch, getState)
|
||||
]);
|
||||
|
||||
const config = configData.data || {};
|
||||
const license = licenseData.data || {};
|
||||
|
||||
if (currentUserId) {
|
||||
if (config.DataRetentionEnableMessageDeletion && config.DataRetentionEnableMessageDeletion === 'true' &&
|
||||
license.IsLicensed === 'true' && license.DataRetention === 'true') {
|
||||
getDataRetentionPolicy()(dispatch, getState);
|
||||
} else {
|
||||
dispatch({type: GeneralTypes.RECEIVED_DATA_RETENTION_POLICY, data: {}});
|
||||
}
|
||||
if (config.DataRetentionEnableMessageDeletion && config.DataRetentionEnableMessageDeletion === 'true' &&
|
||||
license.IsLicensed === 'true' && license.DataRetention === 'true') {
|
||||
getDataRetentionPolicy()(dispatch, getState);
|
||||
} else {
|
||||
dispatch({type: GeneralTypes.RECEIVED_DATA_RETENTION_POLICY, data: {}});
|
||||
}
|
||||
|
||||
return {config, license};
|
||||
@@ -56,7 +53,7 @@ export function loadFromPushNotification(notification) {
|
||||
if (teamId && (!teams[teamId] || !myTeamMembers[teamId])) {
|
||||
await Promise.all([
|
||||
getMyTeams()(dispatch, getState),
|
||||
getMyTeamMembers()(dispatch, getState),
|
||||
getMyTeamMembers()(dispatch, getState)
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -84,7 +81,7 @@ export function purgeOfflineStore() {
|
||||
}
|
||||
|
||||
export function createPost(post) {
|
||||
return (dispatch, getState) => {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const currentUserId = state.entities.users.currentUserId;
|
||||
|
||||
@@ -95,21 +92,26 @@ export function createPost(post) {
|
||||
...post,
|
||||
pending_post_id: pendingPostId,
|
||||
create_at: timestamp,
|
||||
update_at: timestamp,
|
||||
update_at: timestamp
|
||||
};
|
||||
|
||||
return Client4.createPost({...newPost, create_at: 0}).then((payload) => {
|
||||
try {
|
||||
const payload = Client4.createPost({...newPost, create_at: 0});
|
||||
dispatch({
|
||||
type: PostTypes.RECEIVED_POSTS,
|
||||
data: {
|
||||
order: [],
|
||||
posts: {
|
||||
[payload.id]: payload,
|
||||
},
|
||||
[payload.id]: payload
|
||||
}
|
||||
},
|
||||
channelId: payload.channel_id,
|
||||
channelId: payload.channel_id
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -124,5 +126,5 @@ export function recordLoadTime(screenName, category) {
|
||||
export default {
|
||||
loadConfigAndLicense,
|
||||
loadFromPushNotification,
|
||||
purgeOfflineStore,
|
||||
purgeOfflineStore
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ export function handleSearchDraftChanged(text) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ViewTypes.SEARCH_DRAFT_CHANGED,
|
||||
text,
|
||||
text
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {batchActions} from 'redux-batched-actions';
|
||||
import {GeneralTypes} from 'mattermost-redux/action_types';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
export function handleServerUrlChanged(serverUrl) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch(batchActions([
|
||||
{type: GeneralTypes.CLIENT_CONFIG_RESET},
|
||||
{type: GeneralTypes.CLIENT_LICENSE_RESET},
|
||||
{type: ViewTypes.SERVER_URL_CHANGED, serverUrl},
|
||||
]), getState);
|
||||
dispatch({
|
||||
type: ViewTypes.SERVER_URL_CHANGED,
|
||||
serverUrl
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
handleServerUrlChanged,
|
||||
handleServerUrlChanged
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ export function handleTeamChange(teamId, selectChannel = true) {
|
||||
|
||||
const actions = [
|
||||
setChannelDisplayName(''),
|
||||
{type: TeamTypes.SELECT_TEAM, data: teamId},
|
||||
{type: TeamTypes.SELECT_TEAM, data: teamId}
|
||||
];
|
||||
|
||||
if (selectChannel) {
|
||||
@@ -35,7 +35,7 @@ export function handleTeamChange(teamId, selectChannel = true) {
|
||||
markChannelAsRead(lastChannelId, currentChannelId)(dispatch, getState);
|
||||
}
|
||||
|
||||
dispatch(batchActions(actions, 'BATCH_SELECT_TEAM'), getState);
|
||||
dispatch(batchActions(actions), getState);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,5 +55,5 @@ export function selectFirstAvailableTeam() {
|
||||
|
||||
export default {
|
||||
handleTeamChange,
|
||||
selectFirstAvailableTeam,
|
||||
selectFirstAvailableTeam
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ export function handleCommentDraftChanged(rootId, draft) {
|
||||
dispatch({
|
||||
type: ViewTypes.COMMENT_DRAFT_CHANGED,
|
||||
rootId,
|
||||
draft,
|
||||
draft
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
@@ -17,6 +17,6 @@ export function handleCommentDraftSelectionChanged(rootId, cursorPosition) {
|
||||
return {
|
||||
type: ViewTypes.COMMENT_DRAFT_SELECTION_CHANGED,
|
||||
rootId,
|
||||
cursorPosition,
|
||||
cursorPosition
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {userTyping as wsUserTyping} from 'mattermost-redux/actions/websocket';
|
||||
|
||||
export function userTyping(channelId, rootId) {
|
||||
return async (dispatch, getState) => {
|
||||
const {websocket} = getState().device;
|
||||
if (websocket.connected) {
|
||||
wsUserTyping(channelId, rootId)(dispatch, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Alert,
|
||||
Animated,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
export default class AnnouncementBanner extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
dismissBanner: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
allowDismissal: PropTypes.bool,
|
||||
bannerColor: PropTypes.string,
|
||||
bannerDismissed: PropTypes.bool,
|
||||
bannerEnabled: PropTypes.bool,
|
||||
bannerText: PropTypes.string,
|
||||
bannerTextColor: PropTypes.string,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
};
|
||||
|
||||
state = {
|
||||
bannerHeight: new Animated.Value(0),
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
const {bannerDismissed, bannerEnabled, bannerText} = this.props;
|
||||
const showBanner = bannerEnabled && !bannerDismissed && Boolean(bannerText);
|
||||
this.toggleBanner(showBanner);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.bannerText !== nextProps.bannerText ||
|
||||
this.props.bannerEnabled !== nextProps.bannerEnabled ||
|
||||
this.props.bannerDismissed !== nextProps.bannerDismissed) {
|
||||
const showBanner = nextProps.bannerEnabled && !nextProps.bannerDismissed && Boolean(nextProps.bannerText);
|
||||
this.toggleBanner(showBanner);
|
||||
}
|
||||
}
|
||||
|
||||
handleDismiss = () => {
|
||||
const {actions, bannerText} = this.props;
|
||||
actions.dismissBanner(bannerText);
|
||||
};
|
||||
|
||||
handlePress = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const options = [{
|
||||
text: formatMessage({id: 'mobile.announcement_banner.ok', defaultMessage: 'OK'}),
|
||||
}];
|
||||
|
||||
if (this.props.allowDismissal) {
|
||||
options.push({
|
||||
text: formatMessage({id: 'mobile.announcement_banner.dismiss', defaultMessage: 'Dismiss'}),
|
||||
onPress: this.handleDismiss,
|
||||
});
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
formatMessage({id: 'mobile.announcement_banner.title', defaultMessage: 'Announcement'}),
|
||||
this.props.bannerText,
|
||||
options,
|
||||
{cancelable: false}
|
||||
);
|
||||
};
|
||||
|
||||
toggleBanner = (show = true) => {
|
||||
const value = show ? 38 : 0;
|
||||
Animated.timing(this.state.bannerHeight, {
|
||||
toValue: value,
|
||||
duration: 350,
|
||||
}).start();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {bannerHeight} = this.state;
|
||||
|
||||
const bannerStyle = {
|
||||
backgroundColor: this.props.bannerColor,
|
||||
height: bannerHeight,
|
||||
};
|
||||
|
||||
const bannerTextStyle = {
|
||||
color: this.props.bannerTextColor,
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedView
|
||||
style={[style.bannerContainer, bannerStyle]}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={this.handlePress}
|
||||
style={style.wrapper}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={[style.bannerText, bannerTextStyle]}
|
||||
>
|
||||
{this.props.bannerText}
|
||||
</Text>
|
||||
<MaterialIcons
|
||||
color={this.props.bannerTextColor}
|
||||
name='info'
|
||||
size={16}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</AnimatedView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
bannerContainer: {
|
||||
paddingHorizontal: 10,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
},
|
||||
wrapper: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
bannerText: {
|
||||
flex: 1,
|
||||
fontSize: 14,
|
||||
marginRight: 5,
|
||||
},
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import {dismissBanner} from 'app/actions/views/announcement';
|
||||
|
||||
import AnnouncementBanner from './announcement_banner';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const config = getConfig(state);
|
||||
const license = getLicense(state);
|
||||
const {announcement} = state.views;
|
||||
|
||||
return {
|
||||
allowDismissal: config.AllowBannerDismissal === 'true',
|
||||
bannerColor: config.BannerColor,
|
||||
bannerDismissed: config.BannerText === announcement,
|
||||
bannerEnabled: config.EnableBanner === 'true' && license.IsLicensed === 'true',
|
||||
bannerText: config.BannerText,
|
||||
bannerTextColor: config.BannerTextColor,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
dismissBanner,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AnnouncementBanner);
|
||||
@@ -5,14 +5,14 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Svg, {
|
||||
G,
|
||||
Path,
|
||||
Path
|
||||
} from 'react-native-svg';
|
||||
|
||||
export default class AwayStatus extends PureComponent {
|
||||
static propTypes = {
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
color: PropTypes.string.isRequired,
|
||||
color: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Clipboard, Platform, Text} from 'react-native';
|
||||
import {Clipboard, Text} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
@@ -19,11 +19,11 @@ export default class AtMention extends React.PureComponent {
|
||||
onPostPress: PropTypes.func,
|
||||
textStyle: CustomPropTypes.Style,
|
||||
theme: PropTypes.object.isRequired,
|
||||
usersByUsername: PropTypes.object.isRequired,
|
||||
usersByUsername: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
intl: intlShape
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
@@ -32,7 +32,7 @@ export default class AtMention extends React.PureComponent {
|
||||
const userDetails = this.getUserDetailsFromMentionName(props);
|
||||
this.state = {
|
||||
username: userDetails.username,
|
||||
id: userDetails.id,
|
||||
id: userDetails.id
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class AtMention extends React.PureComponent {
|
||||
const userDetails = this.getUserDetailsFromMentionName(nextProps);
|
||||
this.setState({
|
||||
username: userDetails.username,
|
||||
id: userDetails.id,
|
||||
id: userDetails.id
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -49,27 +49,22 @@ export default class AtMention extends React.PureComponent {
|
||||
goToUserProfile = () => {
|
||||
const {navigator, theme} = this.props;
|
||||
const {intl} = this.context;
|
||||
const options = {
|
||||
|
||||
navigator.push({
|
||||
screen: 'UserProfile',
|
||||
title: intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}),
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
passProps: {
|
||||
userId: this.state.id,
|
||||
userId: this.state.id
|
||||
},
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
};
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
navigator.push(options);
|
||||
} else {
|
||||
navigator.showModal(options);
|
||||
}
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getUserDetailsFromMentionName(props) {
|
||||
@@ -80,7 +75,7 @@ export default class AtMention extends React.PureComponent {
|
||||
const user = props.usersByUsername[mentionName];
|
||||
return {
|
||||
username: user.username,
|
||||
id: user.id,
|
||||
id: user.id
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,7 +88,7 @@ export default class AtMention extends React.PureComponent {
|
||||
}
|
||||
|
||||
return {
|
||||
username: '',
|
||||
username: ''
|
||||
};
|
||||
}
|
||||
|
||||
@@ -107,9 +102,9 @@ export default class AtMention extends React.PureComponent {
|
||||
action = {
|
||||
text: intl.formatMessage({
|
||||
id: 'mobile.mention.copy_mention',
|
||||
defaultMessage: 'Copy Mention',
|
||||
defaultMessage: 'Copy Mention'
|
||||
}),
|
||||
onPress: this.handleCopyMention,
|
||||
onPress: this.handleCopyMention
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import AtMention from './at_mention';
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
usersByUsername: getUsersByUsername(state),
|
||||
usersByUsername: getUsersByUsername(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
export default class AtMention extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
autocompleteUsers: PropTypes.func.isRequired,
|
||||
autocompleteUsers: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
currentChannelId: PropTypes.string,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
@@ -33,20 +33,20 @@ export default class AtMention extends PureComponent {
|
||||
requestStatus: PropTypes.string.isRequired,
|
||||
teamMembers: PropTypes.array,
|
||||
theme: PropTypes.object.isRequired,
|
||||
value: PropTypes.string,
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
defaultChannel: {},
|
||||
isSearch: false,
|
||||
value: '',
|
||||
value: ''
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
sections: [],
|
||||
sections: []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export default class AtMention extends PureComponent {
|
||||
// if the term changes but is null or the mention has been completed we render this component as null
|
||||
this.setState({
|
||||
mentionComplete: false,
|
||||
sections: [],
|
||||
sections: []
|
||||
});
|
||||
|
||||
this.props.onResultCountChange(0);
|
||||
@@ -84,7 +84,7 @@ export default class AtMention extends PureComponent {
|
||||
id: 'mobile.suggestion.members',
|
||||
defaultMessage: 'Members',
|
||||
data: teamMembers,
|
||||
key: 'teamMembers',
|
||||
key: 'teamMembers'
|
||||
});
|
||||
} else {
|
||||
if (inChannel.length) {
|
||||
@@ -92,7 +92,7 @@ export default class AtMention extends PureComponent {
|
||||
id: 'suggestion.mention.members',
|
||||
defaultMessage: 'Channel Members',
|
||||
data: inChannel,
|
||||
key: 'inChannel',
|
||||
key: 'inChannel'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ export default class AtMention extends PureComponent {
|
||||
defaultMessage: 'Special Mentions',
|
||||
data: this.getSpecialMentions(),
|
||||
key: 'special',
|
||||
renderItem: this.renderSpecialMentions,
|
||||
renderItem: this.renderSpecialMentions
|
||||
});
|
||||
}
|
||||
|
||||
@@ -111,13 +111,13 @@ export default class AtMention extends PureComponent {
|
||||
id: 'suggestion.mention.nonmembers',
|
||||
defaultMessage: 'Not in Channel',
|
||||
data: outChannel,
|
||||
key: 'outChannel',
|
||||
key: 'outChannel'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
sections,
|
||||
sections
|
||||
});
|
||||
|
||||
this.props.onResultCountChange(sections.reduce((total, section) => total + section.data.length, 0));
|
||||
@@ -134,16 +134,16 @@ export default class AtMention extends PureComponent {
|
||||
id: 'suggestion.mention.all',
|
||||
defaultMessage: 'Notifies everyone in the channel, use in {townsquare} to notify the whole team',
|
||||
values: {
|
||||
townsquare: this.props.defaultChannel.display_name,
|
||||
},
|
||||
townsquare: this.props.defaultChannel.display_name
|
||||
}
|
||||
}, {
|
||||
completeHandle: 'channel',
|
||||
id: 'suggestion.mention.channel',
|
||||
defaultMessage: 'Notifies everyone in the channel',
|
||||
defaultMessage: 'Notifies everyone in the channel'
|
||||
}, {
|
||||
completeHandle: 'here',
|
||||
id: 'suggestion.mention.here',
|
||||
defaultMessage: 'Notifies everyone in the channel and online',
|
||||
defaultMessage: 'Notifies everyone in the channel and online'
|
||||
}];
|
||||
};
|
||||
|
||||
@@ -232,10 +232,10 @@ export default class AtMention extends PureComponent {
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
listView: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
search: {
|
||||
minHeight: 125,
|
||||
},
|
||||
minHeight: 125
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
filterMembersInChannel,
|
||||
filterMembersNotInChannel,
|
||||
filterMembersInCurrentTeam,
|
||||
getMatchTermForAtMention,
|
||||
getMatchTermForAtMention
|
||||
} from 'app/selectors/autocomplete';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
@@ -44,15 +44,15 @@ function mapStateToProps(state, ownProps) {
|
||||
inChannel,
|
||||
outChannel,
|
||||
requestStatus: state.requests.users.autocompleteUsers.status,
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
autocompleteUsers,
|
||||
}, dispatch),
|
||||
autocompleteUsers
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
@@ -19,7 +19,7 @@ export default class AtMentionItem extends PureComponent {
|
||||
onPress: PropTypes.func.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
username: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
completeMention = () => {
|
||||
@@ -33,7 +33,7 @@ export default class AtMentionItem extends PureComponent {
|
||||
lastName,
|
||||
userId,
|
||||
username,
|
||||
theme,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
@@ -67,21 +67,21 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
rowPicture: {
|
||||
marginHorizontal: 8,
|
||||
width: 20,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
rowUsername: {
|
||||
fontSize: 13,
|
||||
color: theme.centerChannelColor,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
rowFullname: {
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.6,
|
||||
},
|
||||
opacity: 0.6
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ function mapStateToProps(state, ownProps) {
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
username: user.username,
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
|
||||
import {
|
||||
Keyboard,
|
||||
Platform,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
|
||||
@@ -24,11 +24,11 @@ export default class Autocomplete extends PureComponent {
|
||||
rootId: PropTypes.string,
|
||||
isSearch: PropTypes.bool,
|
||||
theme: PropTypes.object.isRequired,
|
||||
value: PropTypes.string,
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isSearch: false,
|
||||
isSearch: false
|
||||
};
|
||||
|
||||
state = {
|
||||
@@ -37,12 +37,12 @@ export default class Autocomplete extends PureComponent {
|
||||
channelMentionCount: 0,
|
||||
emojiCount: 0,
|
||||
commandCount: 0,
|
||||
keyboardOffset: 0,
|
||||
keyboardOffset: 0
|
||||
};
|
||||
|
||||
handleSelectionChange = (event) => {
|
||||
this.setState({
|
||||
cursorPosition: event.nativeEvent.selection.end,
|
||||
cursorPosition: event.nativeEvent.selection.end
|
||||
});
|
||||
};
|
||||
|
||||
@@ -147,34 +147,34 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
left: 0,
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
right: 0
|
||||
},
|
||||
borders: {
|
||||
borderWidth: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderBottomWidth: 0,
|
||||
borderBottomWidth: 0
|
||||
},
|
||||
bordersSearch: {
|
||||
borderWidth: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.2)
|
||||
},
|
||||
container: {
|
||||
bottom: 0,
|
||||
maxHeight: 200,
|
||||
maxHeight: 200
|
||||
},
|
||||
content: {
|
||||
flex: 1,
|
||||
flex: 1
|
||||
},
|
||||
searchContainer: {
|
||||
flex: 1,
|
||||
...Platform.select({
|
||||
android: {
|
||||
top: 46,
|
||||
top: 46
|
||||
},
|
||||
ios: {
|
||||
top: 44,
|
||||
},
|
||||
}),
|
||||
},
|
||||
top: 44
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
|
||||
export default class AutocompleteDivider extends PureComponent {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -26,7 +26,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
divider: {
|
||||
height: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
},
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import AutocompleteDivider from './autocomplete_divider';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class AutocompleteSectionHeader extends PureComponent {
|
||||
static propTypes = {
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -40,15 +40,15 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
paddingLeft: 8,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2)
|
||||
},
|
||||
sectionText: {
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.7),
|
||||
paddingVertical: 7,
|
||||
paddingVertical: 7
|
||||
},
|
||||
sectionWrapper: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
backgroundColor: theme.centerChannelBg
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
export default class ChannelMention extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
searchChannels: PropTypes.func.isRequired,
|
||||
searchChannels: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
cursorPosition: PropTypes.number.isRequired,
|
||||
@@ -31,19 +31,19 @@ export default class ChannelMention extends PureComponent {
|
||||
publicChannels: PropTypes.array,
|
||||
requestStatus: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
value: PropTypes.string,
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isSearch: false,
|
||||
value: '',
|
||||
value: ''
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
sections: [],
|
||||
sections: []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export default class ChannelMention extends PureComponent {
|
||||
// if the term changes but is null or the mention has been completed we render this component as null
|
||||
this.setState({
|
||||
mentionComplete: false,
|
||||
sections: [],
|
||||
sections: []
|
||||
});
|
||||
|
||||
this.props.onResultCountChange(0);
|
||||
@@ -83,7 +83,7 @@ export default class ChannelMention extends PureComponent {
|
||||
id: 'suggestion.search.public',
|
||||
defaultMessage: 'Public Channels',
|
||||
data: publicChannels,
|
||||
key: 'publicChannels',
|
||||
key: 'publicChannels'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export default class ChannelMention extends PureComponent {
|
||||
id: 'suggestion.search.private',
|
||||
defaultMessage: 'Private Channels',
|
||||
data: privateChannels,
|
||||
key: 'privateChannels',
|
||||
key: 'privateChannels'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -101,7 +101,7 @@ export default class ChannelMention extends PureComponent {
|
||||
id: 'suggestion.mention.channels',
|
||||
defaultMessage: 'My Channels',
|
||||
data: myChannels,
|
||||
key: 'myChannels',
|
||||
key: 'myChannels'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -110,13 +110,13 @@ export default class ChannelMention extends PureComponent {
|
||||
id: 'suggestion.mention.morechannels',
|
||||
defaultMessage: 'Other Channels',
|
||||
data: otherChannels,
|
||||
key: 'otherChannels',
|
||||
key: 'otherChannels'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
sections,
|
||||
sections
|
||||
});
|
||||
|
||||
this.props.onResultCountChange(sections.reduce((total, section) => total + section.data.length, 0));
|
||||
@@ -196,10 +196,10 @@ export default class ChannelMention extends PureComponent {
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
listView: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
search: {
|
||||
minHeight: 125,
|
||||
},
|
||||
minHeight: 125
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
filterOtherChannels,
|
||||
filterPublicChannels,
|
||||
filterPrivateChannels,
|
||||
getMatchTermForChannelMention,
|
||||
getMatchTermForChannelMention
|
||||
} from 'app/selectors/autocomplete';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
@@ -44,15 +44,15 @@ function mapStateToProps(state, ownProps) {
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
matchTerm,
|
||||
requestStatus: state.requests.channels.getChannels.status,
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
searchChannels,
|
||||
}, dispatch),
|
||||
searchChannels
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
TouchableOpacity
|
||||
} from 'react-native';
|
||||
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
@@ -16,7 +16,7 @@ export default class ChannelMentionItem extends PureComponent {
|
||||
displayName: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
completeMention = () => {
|
||||
@@ -29,7 +29,7 @@ export default class ChannelMentionItem extends PureComponent {
|
||||
channelId,
|
||||
displayName,
|
||||
name,
|
||||
theme,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
@@ -53,15 +53,15 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
padding: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
rowDisplayName: {
|
||||
fontSize: 13,
|
||||
color: theme.centerChannelColor,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
rowName: {
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.6,
|
||||
},
|
||||
opacity: 0.6
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
displayName: channel.display_name,
|
||||
name: channel.name,
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,9 @@ import {
|
||||
FlatList,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
|
||||
|
||||
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
|
||||
import Emoji from 'app/components/emoji';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
@@ -22,37 +20,28 @@ const EMOJI_REGEX_WITHOUT_PREFIX = /\B(:([^:\s]*))$/i;
|
||||
export default class EmojiSuggestion extends Component {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
addReactionToLatestPost: PropTypes.func.isRequired,
|
||||
autocompleteCustomEmojis: PropTypes.func.isRequired,
|
||||
addReactionToLatestPost: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
cursorPosition: PropTypes.number,
|
||||
emojis: PropTypes.array.isRequired,
|
||||
isSearch: PropTypes.bool,
|
||||
fuse: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
onResultCountChange: PropTypes.func.isRequired,
|
||||
rootId: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
serverVersion: PropTypes.string,
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
defaultChannel: {},
|
||||
value: '',
|
||||
value: ''
|
||||
};
|
||||
|
||||
state = {
|
||||
active: false,
|
||||
dataSource: [],
|
||||
dataSource: []
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.matchTerm = '';
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.isSearch) {
|
||||
return;
|
||||
@@ -64,7 +53,8 @@ export default class EmojiSuggestion extends Component {
|
||||
if (!match || this.state.emojiComplete) {
|
||||
this.setState({
|
||||
active: false,
|
||||
emojiComplete: false,
|
||||
matchTerm: null,
|
||||
emojiComplete: false
|
||||
});
|
||||
|
||||
this.props.onResultCountChange(0);
|
||||
@@ -72,57 +62,39 @@ export default class EmojiSuggestion extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldMatchTerm = this.matchTerm;
|
||||
this.matchTerm = match[3] || '';
|
||||
|
||||
// If we're server version 4.7 or higher
|
||||
if (isMinimumServerVersion(this.props.serverVersion, 4, 7)) {
|
||||
if (this.matchTerm !== oldMatchTerm && this.matchTerm.length) {
|
||||
this.props.actions.autocompleteCustomEmojis(this.matchTerm);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.emojis !== nextProps.emojis) {
|
||||
this.handleFuzzySearch(this.matchTerm, nextProps);
|
||||
} else if (!this.matchTerm.length) {
|
||||
const initialEmojis = [...nextProps.emojis];
|
||||
initialEmojis.splice(0, 300);
|
||||
const data = initialEmojis.sort();
|
||||
|
||||
this.setEmojiData(data);
|
||||
}
|
||||
|
||||
return;
|
||||
const matchTerm = match[3];
|
||||
if (matchTerm !== this.state.matchTerm) {
|
||||
this.setState({
|
||||
matchTerm
|
||||
});
|
||||
}
|
||||
|
||||
// If we're server version 4.6 or lower
|
||||
if (this.matchTerm !== oldMatchTerm) {
|
||||
this.handleFuzzySearch(this.matchTerm, nextProps);
|
||||
} else if (!this.matchTerm.length) {
|
||||
let data = [];
|
||||
if (matchTerm.length) {
|
||||
const lowerCaseMatchTerm = matchTerm.toLowerCase();
|
||||
const startsWith = [];
|
||||
const includes = [];
|
||||
nextProps.emojis.forEach((emoji) => {
|
||||
if (emoji.startsWith(lowerCaseMatchTerm)) {
|
||||
startsWith.push(emoji);
|
||||
} else {
|
||||
includes.push(emoji);
|
||||
}
|
||||
});
|
||||
data = [...startsWith.sort(), ...includes.sort()];
|
||||
} else {
|
||||
const initialEmojis = [...nextProps.emojis];
|
||||
initialEmojis.splice(0, 300);
|
||||
const data = initialEmojis.sort();
|
||||
|
||||
this.setEmojiData(data);
|
||||
data = initialEmojis.sort();
|
||||
}
|
||||
}
|
||||
|
||||
handleFuzzySearch = async (matchTerm, props) => {
|
||||
const {emojis, fuse} = props;
|
||||
|
||||
const results = await fuse.search(matchTerm.toLowerCase());
|
||||
const data = results.map((index) => emojis[index]);
|
||||
this.setEmojiData(data);
|
||||
};
|
||||
|
||||
setEmojiData = (data) => {
|
||||
this.setState({
|
||||
active: data.length > 0,
|
||||
dataSource: data,
|
||||
dataSource: data
|
||||
});
|
||||
|
||||
this.props.onResultCountChange(data.length);
|
||||
};
|
||||
}
|
||||
|
||||
completeSuggestion = (emoji) => {
|
||||
const {actions, cursorPosition, onChangeText, value, rootId} = this.props;
|
||||
@@ -143,7 +115,7 @@ export default class EmojiSuggestion extends Component {
|
||||
|
||||
this.setState({
|
||||
active: false,
|
||||
emojiComplete: true,
|
||||
emojiComplete: true
|
||||
});
|
||||
};
|
||||
|
||||
@@ -198,22 +170,22 @@ export default class EmojiSuggestion extends Component {
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
emoji: {
|
||||
marginRight: 5,
|
||||
marginRight: 5
|
||||
},
|
||||
emojiName: {
|
||||
fontSize: 13,
|
||||
color: theme.centerChannelColor,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
listView: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
row: {
|
||||
height: 40,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 8,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
backgroundColor: theme.centerChannelBg
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -6,56 +6,39 @@ import {createSelector} from 'reselect';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
|
||||
import {autocompleteCustomEmojis} from 'mattermost-redux/actions/emojis';
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
|
||||
import {addReactionToLatestPost} from 'app/actions/views/emoji';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {EmojiIndicesByAlias} from 'app/utils/emojis';
|
||||
|
||||
import EmojiSuggestion from './emoji_suggestion';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
const getEmojisByName = createSelector(
|
||||
getCustomEmojisByName,
|
||||
(customEmojis) => {
|
||||
const emoticons = new Set();
|
||||
const emoticons = [];
|
||||
for (const [key] of [...EmojiIndicesByAlias.entries(), ...customEmojis.entries()]) {
|
||||
emoticons.add(key);
|
||||
emoticons.push(key);
|
||||
}
|
||||
|
||||
return Array.from(emoticons);
|
||||
return emoticons;
|
||||
}
|
||||
);
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const options = {
|
||||
shouldSort: true,
|
||||
threshold: 0.3,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
minMatchCharLength: 2,
|
||||
maxPatternLength: 32,
|
||||
};
|
||||
|
||||
const emojis = getEmojisByName(state);
|
||||
const list = emojis.length ? emojis : [];
|
||||
const fuse = new Fuse(list, options);
|
||||
|
||||
return {
|
||||
fuse,
|
||||
emojis,
|
||||
theme: getTheme(state),
|
||||
serverVersion: state.entities.general.serverVersion || Client4.getServerVersion(),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
addReactionToLatestPost,
|
||||
autocompleteCustomEmojis,
|
||||
}, dispatch),
|
||||
addReactionToLatestPost
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ function mapStateToProps(state) {
|
||||
const {deviceHeight} = getDimensions(state);
|
||||
return {
|
||||
deviceHeight,
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -30,15 +30,15 @@ function mapStateToProps(state) {
|
||||
commands: mobileCommandsSelector(state),
|
||||
commandsRequest: state.requests.integrations.getAutocompleteCommands,
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
getAutocompleteCommands,
|
||||
}, dispatch),
|
||||
getAutocompleteCommands
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
FlatList,
|
||||
FlatList
|
||||
} from 'react-native';
|
||||
|
||||
import {RequestStatus} from 'mattermost-redux/constants';
|
||||
@@ -19,7 +19,7 @@ const TIME_BEFORE_NEXT_COMMAND_REQUEST = 1000 * 60 * 5;
|
||||
export default class SlashSuggestion extends Component {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
getAutocompleteCommands: PropTypes.func.isRequired,
|
||||
getAutocompleteCommands: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
commands: PropTypes.array,
|
||||
@@ -28,19 +28,19 @@ export default class SlashSuggestion extends Component {
|
||||
theme: PropTypes.object.isRequired,
|
||||
onChangeText: PropTypes.func.isRequired,
|
||||
onResultCountChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
value: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
defaultChannel: {},
|
||||
value: '',
|
||||
value: ''
|
||||
};
|
||||
|
||||
state = {
|
||||
active: false,
|
||||
suggestionComplete: false,
|
||||
dataSource: [],
|
||||
lastCommandRequest: 0,
|
||||
lastCommandRequest: 0
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
@@ -53,12 +53,12 @@ export default class SlashSuggestion extends Component {
|
||||
commands: nextCommands,
|
||||
commandsRequest: nextCommandsRequest,
|
||||
currentTeamId: nextTeamId,
|
||||
value: nextValue,
|
||||
value: nextValue
|
||||
} = nextProps;
|
||||
|
||||
if (currentTeamId !== nextTeamId) {
|
||||
this.setState({
|
||||
lastCommandRequest: 0,
|
||||
lastCommandRequest: 0
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ export default class SlashSuggestion extends Component {
|
||||
this.setState({
|
||||
active: false,
|
||||
matchTerm: null,
|
||||
suggestionComplete: false,
|
||||
suggestionComplete: false
|
||||
});
|
||||
this.props.onResultCountChange(0);
|
||||
return;
|
||||
@@ -79,7 +79,7 @@ export default class SlashSuggestion extends Component {
|
||||
if ((!nextCommands.length || dataIsStale) && nextCommandsRequest.status !== RequestStatus.STARTED) {
|
||||
this.props.actions.getAutocompleteCommands(nextProps.currentTeamId);
|
||||
this.setState({
|
||||
lastCommandRequest: Date.now(),
|
||||
lastCommandRequest: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export default class SlashSuggestion extends Component {
|
||||
|
||||
this.setState({
|
||||
active: data.length,
|
||||
dataSource: data,
|
||||
dataSource: data
|
||||
});
|
||||
|
||||
this.props.onResultCountChange(data.length);
|
||||
@@ -116,7 +116,7 @@ export default class SlashSuggestion extends Component {
|
||||
|
||||
this.setState({
|
||||
active: false,
|
||||
suggestionComplete: true,
|
||||
suggestionComplete: true
|
||||
});
|
||||
};
|
||||
|
||||
@@ -161,7 +161,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
listView: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
backgroundColor: theme.centerChannelBg
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
TouchableOpacity
|
||||
} from 'react-native';
|
||||
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
@@ -17,7 +17,7 @@ export default class SlashSuggestionItem extends PureComponent {
|
||||
hint: PropTypes.string,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
trigger: PropTypes.string,
|
||||
trigger: PropTypes.string
|
||||
};
|
||||
|
||||
completeSuggestion = () => {
|
||||
@@ -31,7 +31,7 @@ export default class SlashSuggestionItem extends PureComponent {
|
||||
description,
|
||||
hint,
|
||||
theme,
|
||||
trigger,
|
||||
trigger
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
@@ -60,24 +60,24 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
borderLeftWidth: 1,
|
||||
borderLeftColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
borderRightColor: changeOpacity(theme.centerChannelColor, 0.2)
|
||||
},
|
||||
rowDisplayName: {
|
||||
fontSize: 13,
|
||||
color: theme.centerChannelColor,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
rowName: {
|
||||
color: theme.centerChannelColor,
|
||||
opacity: 0.6,
|
||||
opacity: 0.6
|
||||
},
|
||||
suggestionDescription: {
|
||||
fontSize: 11,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.6),
|
||||
color: changeOpacity(theme.centerChannelColor, 0.6)
|
||||
},
|
||||
suggestionName: {
|
||||
fontSize: 13,
|
||||
color: theme.centerChannelColor,
|
||||
marginBottom: 5,
|
||||
},
|
||||
marginBottom: 5
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
@@ -20,7 +20,7 @@ export default class SpecialMentionItem extends PureComponent {
|
||||
id: PropTypes.string.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
values: PropTypes.object,
|
||||
values: PropTypes.object
|
||||
};
|
||||
|
||||
completeMention = () => {
|
||||
@@ -34,7 +34,7 @@ export default class SpecialMentionItem extends PureComponent {
|
||||
id,
|
||||
completeHandle,
|
||||
theme,
|
||||
values,
|
||||
values
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
@@ -70,31 +70,31 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
rowPicture: {
|
||||
marginHorizontal: 8,
|
||||
width: 20,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
rowIcon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.7),
|
||||
fontSize: 14,
|
||||
fontSize: 14
|
||||
},
|
||||
rowUsername: {
|
||||
fontSize: 13,
|
||||
color: theme.centerChannelColor,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
rowFullname: {
|
||||
color: theme.centerChannelColor,
|
||||
flex: 1,
|
||||
opacity: 0.6,
|
||||
opacity: 0.6
|
||||
},
|
||||
textWrapper: {
|
||||
flex: 1,
|
||||
flexWrap: 'wrap',
|
||||
paddingRight: 8,
|
||||
},
|
||||
paddingRight: 8
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -9,14 +9,14 @@ import {
|
||||
Text,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ViewPropTypes,
|
||||
ViewPropTypes
|
||||
} from 'react-native';
|
||||
|
||||
export default class Badge extends PureComponent {
|
||||
static defaultProps = {
|
||||
extraPaddingHorizontal: 10,
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
minWidth: 0
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
@@ -26,7 +26,7 @@ export default class Badge extends PureComponent {
|
||||
countStyle: Text.propTypes.style,
|
||||
minHeight: PropTypes.number,
|
||||
minWidth: PropTypes.number,
|
||||
onPress: PropTypes.func,
|
||||
onPress: PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -42,7 +42,7 @@ export default class Badge extends PureComponent {
|
||||
onMoveShouldSetPanResponder: () => true,
|
||||
onStartShouldSetResponderCapture: () => true,
|
||||
onMoveShouldSetResponderCapture: () => true,
|
||||
onResponderMove: () => false,
|
||||
onResponderMove: () => false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ export default class Badge extends PureComponent {
|
||||
|
||||
onLayout = (e) => {
|
||||
if (!this.layoutReady) {
|
||||
const height = Math.max(e.nativeEvent.layout.height, this.props.minHeight);
|
||||
const borderRadius = height / 2;
|
||||
let width;
|
||||
|
||||
if (e.nativeEvent.layout.width <= e.nativeEvent.layout.height) {
|
||||
@@ -81,14 +83,14 @@ export default class Badge extends PureComponent {
|
||||
} else {
|
||||
width = e.nativeEvent.layout.width + this.props.extraPaddingHorizontal;
|
||||
}
|
||||
width = Math.max(width + 10, this.props.minWidth);
|
||||
const borderRadius = width / 2;
|
||||
width = Math.max(width, this.props.minWidth);
|
||||
this.setNativeProps({
|
||||
style: {
|
||||
width,
|
||||
height,
|
||||
borderRadius,
|
||||
opacity: 1,
|
||||
},
|
||||
opacity: 1
|
||||
}
|
||||
});
|
||||
this.layoutReady = true;
|
||||
}
|
||||
@@ -139,23 +141,22 @@ export default class Badge extends PureComponent {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
badge: {
|
||||
backgroundColor: '#444',
|
||||
borderRadius: 20,
|
||||
height: 20,
|
||||
top: 2,
|
||||
padding: 12,
|
||||
paddingTop: 3,
|
||||
paddingBottom: 3,
|
||||
backgroundColor: '#444',
|
||||
borderRadius: 20,
|
||||
position: 'absolute',
|
||||
right: 30,
|
||||
top: 2,
|
||||
right: 30
|
||||
},
|
||||
wrapper: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
text: {
|
||||
fontSize: 14,
|
||||
color: 'white',
|
||||
},
|
||||
color: 'white'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,19 +11,19 @@ import {GlobalStyles} from 'app/styles';
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
loading: {
|
||||
marginLeft: 3,
|
||||
},
|
||||
marginLeft: 3
|
||||
}
|
||||
});
|
||||
|
||||
export default class Button extends PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
loading: PropTypes.bool,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
onPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
onPress = () => {
|
||||
|
||||
@@ -9,15 +9,11 @@ import {
|
||||
Keyboard,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import {General, WebsocketEvents} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
|
||||
import Drawer from 'app/components/drawer';
|
||||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {alertErrorWithFallback} from 'app/utils/general';
|
||||
import tracker from 'app/utils/time_tracker';
|
||||
|
||||
@@ -25,12 +21,9 @@ import ChannelsList from './channels_list';
|
||||
import DrawerSwiper from './drawer_swipper';
|
||||
import TeamsList from './teams_list';
|
||||
|
||||
const {
|
||||
ANDROID_TOP_LANDSCAPE,
|
||||
ANDROID_TOP_PORTRAIT,
|
||||
IOS_TOP_LANDSCAPE,
|
||||
IOS_TOP_PORTRAIT,
|
||||
} = ViewTypes;
|
||||
import {General, WebsocketEvents} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
const DRAWER_INITIAL_OFFSET = 40;
|
||||
const DRAWER_LANDSCAPE_OFFSET = 150;
|
||||
|
||||
@@ -43,7 +36,7 @@ export default class ChannelDrawer extends Component {
|
||||
makeDirectChannel: PropTypes.func.isRequired,
|
||||
markChannelAsRead: PropTypes.func.isRequired,
|
||||
setChannelDisplayName: PropTypes.func.isRequired,
|
||||
setChannelLoading: PropTypes.func.isRequired,
|
||||
setChannelLoading: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
blurPostTextBox: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
@@ -54,11 +47,11 @@ export default class ChannelDrawer extends Component {
|
||||
intl: PropTypes.object.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
teamsCount: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
closeHandle = null;
|
||||
openHandle = null;
|
||||
closeLeftHandle = null;
|
||||
openLeftHandle = null;
|
||||
swiperIndex = 1;
|
||||
|
||||
constructor(props) {
|
||||
@@ -69,7 +62,7 @@ export default class ChannelDrawer extends Component {
|
||||
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
|
||||
}
|
||||
this.state = {
|
||||
openDrawerOffset,
|
||||
openDrawerOffset
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,6 +71,7 @@ export default class ChannelDrawer extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
EventEmitter.on('open_channel_drawer', this.openChannelDrawer);
|
||||
EventEmitter.on('close_channel_drawer', this.closeChannelDrawer);
|
||||
EventEmitter.on(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
|
||||
BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack);
|
||||
@@ -110,6 +104,7 @@ export default class ChannelDrawer extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('open_channel_drawer', this.openChannelDrawer);
|
||||
EventEmitter.off('close_channel_drawer', this.closeChannelDrawer);
|
||||
EventEmitter.off(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack);
|
||||
@@ -137,15 +132,15 @@ export default class ChannelDrawer extends Component {
|
||||
handleDrawerClose = () => {
|
||||
this.resetDrawer();
|
||||
|
||||
if (this.closeHandle) {
|
||||
InteractionManager.clearInteractionHandle(this.closeHandle);
|
||||
this.closeHandle = null;
|
||||
if (this.closeLeftHandle) {
|
||||
InteractionManager.clearInteractionHandle(this.closeLeftHandle);
|
||||
this.closeLeftHandle = null;
|
||||
}
|
||||
};
|
||||
|
||||
handleDrawerCloseStart = () => {
|
||||
if (!this.closeHandle) {
|
||||
this.closeHandle = InteractionManager.createInteractionHandle();
|
||||
if (!this.closeLeftHandle) {
|
||||
this.closeLeftHandle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -154,15 +149,15 @@ export default class ChannelDrawer extends Component {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
|
||||
if (this.openHandle) {
|
||||
InteractionManager.clearInteractionHandle(this.openHandle);
|
||||
this.openHandle = null;
|
||||
if (this.openLeftHandle) {
|
||||
InteractionManager.clearInteractionHandle(this.openLeftHandle);
|
||||
this.openLeftHandle = null;
|
||||
}
|
||||
};
|
||||
|
||||
handleDrawerOpenStart = () => {
|
||||
if (!this.openHandle) {
|
||||
this.openHandle = InteractionManager.createInteractionHandle();
|
||||
if (!this.openLeftHandle) {
|
||||
this.openLeftHandle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -175,12 +170,12 @@ export default class ChannelDrawer extends Component {
|
||||
mainOverlay: {
|
||||
backgroundColor: this.props.theme.centerChannelBg,
|
||||
elevation: 3,
|
||||
opacity,
|
||||
opacity
|
||||
},
|
||||
drawerOverlay: {
|
||||
backgroundColor: ratio ? '#000' : '#FFF',
|
||||
opacity: ratio ? (1 - ratio) / 2 : 1,
|
||||
},
|
||||
opacity: ratio ? (1 - ratio) / 2 : 1
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -202,7 +197,7 @@ export default class ChannelDrawer extends Component {
|
||||
|
||||
selectChannel = (channel, currentChannelId) => {
|
||||
const {
|
||||
actions,
|
||||
actions
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -210,17 +205,16 @@ export default class ChannelDrawer extends Component {
|
||||
markChannelAsRead,
|
||||
setChannelLoading,
|
||||
setChannelDisplayName,
|
||||
markChannelAsViewed,
|
||||
markChannelAsViewed
|
||||
} = actions;
|
||||
|
||||
tracker.channelSwitch = Date.now();
|
||||
setChannelLoading(channel.id !== currentChannelId);
|
||||
setChannelDisplayName(channel.display_name);
|
||||
|
||||
this.closeChannelDrawer();
|
||||
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
setChannelLoading(channel.id !== currentChannelId);
|
||||
setChannelDisplayName(channel.display_name);
|
||||
|
||||
handleSelectChannel(channel.id);
|
||||
requestAnimationFrame(() => {
|
||||
// mark the channel as viewed after all the frame has flushed
|
||||
@@ -237,12 +231,12 @@ export default class ChannelDrawer extends Component {
|
||||
actions,
|
||||
currentTeamId,
|
||||
currentUserId,
|
||||
intl,
|
||||
intl
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
joinChannel,
|
||||
makeDirectChannel,
|
||||
makeDirectChannel
|
||||
} = actions;
|
||||
|
||||
const displayValue = {displayName: channel.display_name};
|
||||
@@ -254,7 +248,7 @@ export default class ChannelDrawer extends Component {
|
||||
if (result.error) {
|
||||
const dmFailedMessage = {
|
||||
id: 'mobile.open_dm.error',
|
||||
defaultMessage: "We couldn't open a direct message with {displayName}. Please check your connection and try again.",
|
||||
defaultMessage: "We couldn't open a direct message with {displayName}. Please check your connection and try again."
|
||||
};
|
||||
alertErrorWithFallback(intl, result.error, dmFailedMessage, displayValue);
|
||||
}
|
||||
@@ -264,7 +258,7 @@ export default class ChannelDrawer extends Component {
|
||||
if (result.error) {
|
||||
const joinFailedMessage = {
|
||||
id: 'mobile.join_channel.error',
|
||||
defaultMessage: "We couldn't join the channel {displayName}. Please check your connection and try again.",
|
||||
defaultMessage: "We couldn't join the channel {displayName}. Please check your connection and try again."
|
||||
};
|
||||
alertErrorWithFallback(intl, result.error, joinFailedMessage, displayValue);
|
||||
}
|
||||
@@ -320,11 +314,11 @@ export default class ChannelDrawer extends Component {
|
||||
const {
|
||||
navigator,
|
||||
teamsCount,
|
||||
theme,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
openDrawerOffset,
|
||||
openDrawerOffset
|
||||
} = this.state;
|
||||
|
||||
const multipleTeams = teamsCount > 1;
|
||||
@@ -373,7 +367,6 @@ export default class ChannelDrawer extends Component {
|
||||
return (
|
||||
<SafeAreaView
|
||||
backgroundColor={theme.sidebarHeaderBg}
|
||||
footerColor={theme.sidebarHeaderBg}
|
||||
navigator={navigator}
|
||||
>
|
||||
<DrawerSwiper
|
||||
@@ -389,12 +382,9 @@ export default class ChannelDrawer extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {children, isLandscape} = this.props;
|
||||
const {children} = this.props;
|
||||
const {openDrawerOffset} = this.state;
|
||||
|
||||
const androidTop = isLandscape ? ANDROID_TOP_LANDSCAPE : ANDROID_TOP_PORTRAIT;
|
||||
const iosTop = isLandscape ? IOS_TOP_LANDSCAPE : IOS_TOP_PORTRAIT;
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
ref='drawer'
|
||||
@@ -420,8 +410,8 @@ export default class ChannelDrawer extends Component {
|
||||
tweenDuration={100}
|
||||
tweenHandler={this.handleDrawerTween}
|
||||
elevation={-5}
|
||||
bottomPanOffset={Platform.OS === 'ios' ? ANDROID_TOP_LANDSCAPE : IOS_TOP_PORTRAIT}
|
||||
topPanOffset={Platform.OS === 'ios' ? iosTop : androidTop}
|
||||
bottomPanOffset={Platform.OS === 'ios' ? 46 : 64}
|
||||
topPanOffset={Platform.OS === 'ios' ? 64 : 46}
|
||||
styles={{
|
||||
main: {
|
||||
shadowColor: '#000000',
|
||||
@@ -429,9 +419,9 @@ export default class ChannelDrawer extends Component {
|
||||
shadowRadius: 12,
|
||||
shadowOffset: {
|
||||
width: -4,
|
||||
height: 0,
|
||||
},
|
||||
},
|
||||
height: 0
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -443,6 +433,6 @@ export default class ChannelDrawer extends Component {
|
||||
const style = StyleSheet.create({
|
||||
swiperContent: {
|
||||
flex: 1,
|
||||
marginBottom: 10,
|
||||
},
|
||||
marginBottom: 10
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,13 +8,13 @@ import {
|
||||
Platform,
|
||||
TouchableHighlight,
|
||||
Text,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import Badge from 'app/components/badge';
|
||||
import ChannelIcon from 'app/components/channel_icon';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
@@ -25,22 +25,20 @@ export default class ChannelItem extends PureComponent {
|
||||
currentChannelId: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string.isRequired,
|
||||
fake: PropTypes.bool,
|
||||
isMyUser: PropTypes.bool,
|
||||
isUnread: PropTypes.bool,
|
||||
mentions: PropTypes.number.isRequired,
|
||||
navigator: PropTypes.object,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
status: PropTypes.string,
|
||||
teammateDeletedAt: PropTypes.number,
|
||||
type: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
intl: intlShape
|
||||
};
|
||||
|
||||
onPress = preventDoubleTap(() => {
|
||||
onPress = wrapWithPreventDoubleTap(() => {
|
||||
const {channelId, currentChannelId, displayName, fake, onSelectChannel, type} = this.props;
|
||||
requestAnimationFrame(() => {
|
||||
onSelectChannel({id: channelId, display_name: displayName, fake, type}, currentChannelId);
|
||||
@@ -58,11 +56,11 @@ export default class ChannelItem extends PureComponent {
|
||||
previewView: this.previewRef,
|
||||
previewActions: [{
|
||||
id: 'action-mark-as-read',
|
||||
title: intl.formatMessage({id: 'mobile.channel.markAsRead', defaultMessage: 'Mark As Read'}),
|
||||
title: intl.formatMessage({id: 'mobile.channel.markAsRead', defaultMessage: 'Mark As Read'})
|
||||
}],
|
||||
passProps: {
|
||||
channelId,
|
||||
},
|
||||
channelId
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -76,25 +74,13 @@ export default class ChannelItem extends PureComponent {
|
||||
channelId,
|
||||
currentChannelId,
|
||||
displayName,
|
||||
isMyUser,
|
||||
isUnread,
|
||||
mentions,
|
||||
status,
|
||||
teammateDeletedAt,
|
||||
theme,
|
||||
type,
|
||||
type
|
||||
} = this.props;
|
||||
|
||||
const {intl} = this.context;
|
||||
|
||||
let channelDisplayName = displayName;
|
||||
if (isMyUser) {
|
||||
channelDisplayName = intl.formatMessage({
|
||||
id: 'channel_header.directchannel.you',
|
||||
defaultMessage: '{displayName} (you)',
|
||||
}, {displayname: displayName});
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
const isActive = channelId === currentChannelId;
|
||||
|
||||
@@ -135,7 +121,6 @@ export default class ChannelItem extends PureComponent {
|
||||
membersCount={displayName.split(',').length}
|
||||
size={16}
|
||||
status={status}
|
||||
teammateDeletedAt={teammateDeletedAt}
|
||||
theme={theme}
|
||||
type={type}
|
||||
/>
|
||||
@@ -157,7 +142,7 @@ export default class ChannelItem extends PureComponent {
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{channelDisplayName}
|
||||
{displayName}
|
||||
</Text>
|
||||
{badge}
|
||||
</View>
|
||||
@@ -173,22 +158,22 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
height: 44,
|
||||
height: 44
|
||||
},
|
||||
borderActive: {
|
||||
backgroundColor: theme.sidebarTextActiveBorder,
|
||||
width: 5,
|
||||
width: 5
|
||||
},
|
||||
item: {
|
||||
alignItems: 'center',
|
||||
height: 44,
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
paddingLeft: 16,
|
||||
paddingLeft: 16
|
||||
},
|
||||
itemActive: {
|
||||
backgroundColor: changeOpacity(theme.sidebarTextActiveColor, 0.1),
|
||||
paddingLeft: 11,
|
||||
paddingLeft: 11
|
||||
},
|
||||
text: {
|
||||
color: changeOpacity(theme.sidebarText, 0.4),
|
||||
@@ -196,13 +181,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
lineHeight: 16,
|
||||
paddingRight: 40,
|
||||
paddingRight: 40
|
||||
},
|
||||
textActive: {
|
||||
color: theme.sidebarTextActiveColor,
|
||||
color: theme.sidebarTextActiveColor
|
||||
},
|
||||
textUnread: {
|
||||
color: theme.sidebarUnreadText,
|
||||
color: theme.sidebarUnreadText
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: theme.mentionBj,
|
||||
@@ -211,11 +196,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
borderWidth: 1,
|
||||
padding: 3,
|
||||
position: 'relative',
|
||||
right: 16,
|
||||
right: 16
|
||||
},
|
||||
mention: {
|
||||
color: theme.mentionColor,
|
||||
fontSize: 10,
|
||||
},
|
||||
fontSize: 10
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {getCurrentChannelId, makeGetChannel, getMyChannelMember} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import ChannelItem from './channel_item';
|
||||
|
||||
@@ -20,27 +18,14 @@ function makeMapStateToProps() {
|
||||
member = getMyChannelMember(state, ownProps.channelId);
|
||||
}
|
||||
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
let isMyUser = false;
|
||||
let teammateDeletedAt = 0;
|
||||
if (channel.type === General.DM_CHANNEL && channel.teammate_id) {
|
||||
isMyUser = channel.teammate_id === currentUserId;
|
||||
const teammate = getUser(state, channel.teammate_id);
|
||||
if (teammate && teammate.delete_at) {
|
||||
teammateDeletedAt = teammate.delete_at;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
currentChannelId: getCurrentChannelId(state),
|
||||
displayName: channel.display_name,
|
||||
fake: channel.fake,
|
||||
isMyUser,
|
||||
mentions: member ? member.mention_count : 0,
|
||||
status: channel.status,
|
||||
teammateDeletedAt,
|
||||
theme: getTheme(state),
|
||||
type: channel.type,
|
||||
type: channel.type
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,21 +5,21 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Platform,
|
||||
View,
|
||||
TouchableHighlight,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import FilteredList from './filtered_list';
|
||||
import List from './list';
|
||||
import SwitchTeamsButton from './switch_teams_button';
|
||||
|
||||
const {ANDROID_TOP_PORTRAIT} = ViewTypes;
|
||||
|
||||
class ChannelsList extends React.PureComponent {
|
||||
static propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
@@ -29,7 +29,7 @@ class ChannelsList extends React.PureComponent {
|
||||
onSearchStart: PropTypes.func.isRequired,
|
||||
onSelectChannel: PropTypes.func.isRequired,
|
||||
onShowTeams: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -37,7 +37,7 @@ class ChannelsList extends React.PureComponent {
|
||||
|
||||
this.state = {
|
||||
searching: false,
|
||||
term: '',
|
||||
term: ''
|
||||
};
|
||||
|
||||
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
|
||||
@@ -57,6 +57,30 @@ class ChannelsList extends React.PureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
openSettingsModal = wrapWithPreventDoubleTap(() => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
screen: 'Settings',
|
||||
title: intl.formatMessage({id: 'mobile.routes.settings', defaultMessage: 'Settings'}),
|
||||
animationType: 'slide-up',
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'close-settings',
|
||||
icon: this.closeButton
|
||||
}]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onSearch = (term) => {
|
||||
this.setState({term});
|
||||
};
|
||||
@@ -77,12 +101,13 @@ class ChannelsList extends React.PureComponent {
|
||||
intl,
|
||||
navigator,
|
||||
onShowTeams,
|
||||
theme,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
const {searching, term} = this.state;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
let settings;
|
||||
let list;
|
||||
if (searching) {
|
||||
list = (
|
||||
@@ -93,6 +118,19 @@ class ChannelsList extends React.PureComponent {
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
settings = (
|
||||
<TouchableHighlight
|
||||
style={styles.settingsContainer}
|
||||
onPress={this.openSettingsModal}
|
||||
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
|
||||
>
|
||||
<AwesomeIcon
|
||||
name='cog'
|
||||
style={styles.settings}
|
||||
/>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
|
||||
list = (
|
||||
<List
|
||||
navigator={navigator}
|
||||
@@ -106,11 +144,7 @@ class ChannelsList extends React.PureComponent {
|
||||
backgroundColor: changeOpacity(theme.sidebarHeaderTextColor, 0.2),
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 15,
|
||||
...Platform.select({
|
||||
android: {
|
||||
marginBottom: -5,
|
||||
},
|
||||
}),
|
||||
lineHeight: 66
|
||||
};
|
||||
|
||||
const title = (
|
||||
@@ -120,7 +154,7 @@ class ChannelsList extends React.PureComponent {
|
||||
placeholder={intl.formatMessage({id: 'mobile.channel_drawer.search', defaultMessage: 'Jump to...'})}
|
||||
cancelTitle={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
backgroundColor='transparent'
|
||||
inputHeight={34}
|
||||
inputHeight={33}
|
||||
inputStyle={searchBarInput}
|
||||
placeholderTextColor={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
tintColorSearch={changeOpacity(theme.sidebarHeaderTextColor, 0.5)}
|
||||
@@ -149,6 +183,7 @@ class ChannelsList extends React.PureComponent {
|
||||
/>
|
||||
</View>
|
||||
{title}
|
||||
{settings}
|
||||
</View>
|
||||
</View>
|
||||
{list}
|
||||
@@ -161,10 +196,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.sidebarBg,
|
||||
flex: 1,
|
||||
flex: 1
|
||||
},
|
||||
statusBar: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
backgroundColor: theme.sidebarHeaderBg
|
||||
},
|
||||
headerContainer: {
|
||||
alignItems: 'center',
|
||||
@@ -175,30 +210,50 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
borderBottomColor: changeOpacity(theme.sidebarHeaderTextColor, 0.10),
|
||||
...Platform.select({
|
||||
android: {
|
||||
height: ANDROID_TOP_PORTRAIT,
|
||||
height: 46
|
||||
},
|
||||
ios: {
|
||||
height: 44,
|
||||
},
|
||||
}),
|
||||
height: 44
|
||||
}
|
||||
})
|
||||
},
|
||||
header: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
fontWeight: 'normal',
|
||||
paddingLeft: 16,
|
||||
paddingLeft: 16
|
||||
},
|
||||
switchContainer: {
|
||||
position: 'relative',
|
||||
top: -1,
|
||||
top: -1
|
||||
},
|
||||
settingsContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 10,
|
||||
...Platform.select({
|
||||
android: {
|
||||
height: 46,
|
||||
marginRight: 6
|
||||
},
|
||||
ios: {
|
||||
height: 44,
|
||||
marginRight: 8
|
||||
}
|
||||
})
|
||||
},
|
||||
settings: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 18,
|
||||
fontWeight: '300'
|
||||
},
|
||||
titleContainer: { // These aren't used by this component, but they are passed down to the list component
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
height: 48,
|
||||
marginLeft: 16,
|
||||
marginLeft: 16
|
||||
},
|
||||
title: {
|
||||
flex: 1,
|
||||
@@ -207,40 +262,39 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
fontSize: 15,
|
||||
fontWeight: '400',
|
||||
letterSpacing: 0.8,
|
||||
lineHeight: 18,
|
||||
lineHeight: 18
|
||||
},
|
||||
searchContainer: {
|
||||
flex: 1,
|
||||
paddingRight: 10,
|
||||
...Platform.select({
|
||||
android: {
|
||||
marginBottom: 1,
|
||||
marginBottom: 1
|
||||
},
|
||||
ios: {
|
||||
marginBottom: 3,
|
||||
},
|
||||
}),
|
||||
marginBottom: 3
|
||||
}
|
||||
})
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.sidebarText, 0.1),
|
||||
height: 1,
|
||||
height: 1
|
||||
},
|
||||
actionContainer: {
|
||||
alignItems: 'center',
|
||||
height: 48,
|
||||
justifyContent: 'center',
|
||||
width: 50,
|
||||
width: 50
|
||||
},
|
||||
action: {
|
||||
color: theme.sidebarText,
|
||||
fontSize: 20,
|
||||
fontWeight: '500',
|
||||
lineHeight: 18,
|
||||
lineHeight: 18
|
||||
},
|
||||
above: {
|
||||
backgroundColor: theme.mentionBj,
|
||||
top: 9,
|
||||
},
|
||||
top: 9
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
FlatList,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
@@ -20,9 +20,6 @@ import {sortChannelsByDisplayName} from 'mattermost-redux/utils/channel_utils';
|
||||
import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import ChannelDrawerItem from 'app/components/channel_drawer/channels_list/channel_item';
|
||||
import {ListTypes} from 'app/constants';
|
||||
|
||||
const VIEWABILITY_CONFIG = ListTypes.VISIBILITY_CONFIG_DEFAULTS;
|
||||
|
||||
class FilteredList extends Component {
|
||||
static propTypes = {
|
||||
@@ -30,7 +27,7 @@ class FilteredList extends Component {
|
||||
getProfilesInTeam: PropTypes.func.isRequired,
|
||||
makeGroupMessageVisibleIfNecessary: PropTypes.func.isRequired,
|
||||
searchChannels: PropTypes.func.isRequired,
|
||||
searchProfiles: PropTypes.func.isRequired,
|
||||
searchProfiles: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
channels: PropTypes.object.isRequired,
|
||||
currentTeam: PropTypes.object.isRequired,
|
||||
@@ -49,18 +46,19 @@ class FilteredList extends Component {
|
||||
statuses: PropTypes.object,
|
||||
styles: PropTypes.object.isRequired,
|
||||
term: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
currentTeam: {},
|
||||
currentChannel: {},
|
||||
pastDirectMessages: [],
|
||||
pastDirectMessages: []
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
dataSource: this.buildData(props),
|
||||
dataSource: this.buildData(props)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -145,28 +143,28 @@ class FilteredList extends Component {
|
||||
unreads: {
|
||||
builder: this.buildUnreadChannelsForSearch,
|
||||
id: 'mobile.channel_list.unreads',
|
||||
defaultMessage: 'UNREADS',
|
||||
defaultMessage: 'UNREADS'
|
||||
},
|
||||
channels: {
|
||||
builder: this.buildChannelsForSearch,
|
||||
id: 'mobile.channel_list.channels',
|
||||
defaultMessage: 'CHANNELS',
|
||||
defaultMessage: 'CHANNELS'
|
||||
},
|
||||
dms: {
|
||||
builder: this.buildCurrentDMSForSearch,
|
||||
id: 'sidebar.direct',
|
||||
defaultMessage: 'DIRECT MESSAGES',
|
||||
defaultMessage: 'DIRECT MESSAGES'
|
||||
},
|
||||
members: {
|
||||
builder: this.buildMembersForSearch,
|
||||
id: 'mobile.channel_list.members',
|
||||
defaultMessage: 'MEMBERS',
|
||||
defaultMessage: 'MEMBERS'
|
||||
},
|
||||
nonmembers: {
|
||||
builder: this.buildOtherMembersForSearch,
|
||||
id: 'mobile.channel_list.not_member',
|
||||
defaultMessage: 'NOT A MEMBER',
|
||||
},
|
||||
defaultMessage: 'NOT A MEMBER'
|
||||
}
|
||||
});
|
||||
|
||||
buildUnreadChannelsForSearch = (props, term) => {
|
||||
@@ -211,14 +209,14 @@ class FilteredList extends Component {
|
||||
type: General.DM_CHANNEL,
|
||||
fake: true,
|
||||
nickname: u.nickname,
|
||||
fullname: `${u.first_name} ${u.last_name}`,
|
||||
fullname: `${u.first_name} ${u.last_name}`
|
||||
};
|
||||
});
|
||||
|
||||
groupChannels = groupChannels.map((channel) => {
|
||||
return {
|
||||
...channel,
|
||||
...groupChannelMemberDetails[channel.id],
|
||||
...groupChannelMemberDetails[channel.id]
|
||||
};
|
||||
});
|
||||
|
||||
@@ -252,7 +250,7 @@ class FilteredList extends Component {
|
||||
type: General.DM_CHANNEL,
|
||||
fake: true,
|
||||
nickname: u.nickname,
|
||||
fullname: `${u.first_name} ${u.last_name}`,
|
||||
fullname: `${u.first_name} ${u.last_name}`
|
||||
};
|
||||
});
|
||||
|
||||
@@ -265,7 +263,7 @@ class FilteredList extends Component {
|
||||
const {
|
||||
favoriteChannels,
|
||||
publicChannels,
|
||||
privateChannels,
|
||||
privateChannels
|
||||
} = props.channels;
|
||||
|
||||
const favorites = favoriteChannels.filter((c) => {
|
||||
@@ -282,7 +280,7 @@ class FilteredList extends Component {
|
||||
const notMemberOf = otherChannels.map((o) => {
|
||||
return {
|
||||
...o,
|
||||
fake: true,
|
||||
fake: true
|
||||
};
|
||||
});
|
||||
|
||||
@@ -368,7 +366,7 @@ class FilteredList extends Component {
|
||||
</View>
|
||||
{bottomDivider && this.renderDivider(styles, 16)}
|
||||
</View>
|
||||
),
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -389,7 +387,10 @@ class FilteredList extends Component {
|
||||
onViewableItemsChanged={this.updateUnreadIndicators}
|
||||
keyboardDismissMode='on-drag'
|
||||
maxToRenderPerBatch={10}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
viewabilityConfig={{
|
||||
viewAreaCoveragePercentThreshold: 3,
|
||||
waitForInteraction: false
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -13,10 +13,9 @@ import {
|
||||
getChannelsWithUnreadSection,
|
||||
getCurrentChannel,
|
||||
getGroupChannels,
|
||||
getOtherChannels,
|
||||
getOtherChannels
|
||||
} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUserId, getProfilesInCurrentTeam, getUsers, getUserIdsInChannels, getUserStatuses} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getDirectShowPreferences, getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
@@ -73,14 +72,14 @@ function getGroupDetails(currentUserId, userIdsInChannels, profiles, groupChanne
|
||||
email: [],
|
||||
fullname: [],
|
||||
nickname: [],
|
||||
username: [],
|
||||
username: []
|
||||
});
|
||||
|
||||
groupMemberDetails[channel.id] = {
|
||||
email: members.email.join(','),
|
||||
fullname: members.fullname.join(','),
|
||||
nickname: members.nickname.join(','),
|
||||
username: members.username.join(','),
|
||||
username: members.username.join(',')
|
||||
};
|
||||
|
||||
return groupMemberDetails;
|
||||
@@ -110,7 +109,6 @@ function mapStateToProps(state) {
|
||||
return {
|
||||
channels: getChannelsWithUnreadSection(state),
|
||||
currentChannel: getCurrentChannel(state),
|
||||
currentTeam: getCurrentTeam(state),
|
||||
currentUserId,
|
||||
otherChannels: getOtherChannels(state),
|
||||
groupChannelMemberDetails: getGroupChannelMemberDetails(state),
|
||||
@@ -121,7 +119,7 @@ function mapStateToProps(state) {
|
||||
searchOrder,
|
||||
pastDirectMessages: pastDirectMessages(state),
|
||||
restrictDms,
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -131,8 +129,8 @@ function mapDispatchToProps(dispatch) {
|
||||
getProfilesInTeam,
|
||||
makeGroupMessageVisibleIfNecessary,
|
||||
searchChannels,
|
||||
searchProfiles,
|
||||
}, dispatch),
|
||||
searchProfiles
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import ChannelsList from './channels_list';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
getSortedFavoriteChannelIds,
|
||||
getSortedPublicChannelIds,
|
||||
getSortedPrivateChannelIds,
|
||||
getSortedDirectChannelIds,
|
||||
getSortedDirectChannelIds
|
||||
} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getTheme, getFavoritesPreferences} from 'mattermost-redux/selectors/entities/preferences';
|
||||
@@ -34,7 +34,7 @@ function mapStateToProps(state) {
|
||||
publicChannelIds,
|
||||
privateChannelIds,
|
||||
directChannelIds,
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
SectionList,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
@@ -18,15 +18,9 @@ import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
|
||||
import ChannelItem from 'app/components/channel_drawer/channels_list/channel_item';
|
||||
import UnreadIndicator from 'app/components/channel_drawer/channels_list/unread_indicator';
|
||||
import {ListTypes} from 'app/constants';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
|
||||
const VIEWABILITY_CONFIG = {
|
||||
...ListTypes.VISIBILITY_CONFIG_DEFAULTS,
|
||||
waitForInteraction: true,
|
||||
};
|
||||
|
||||
export default class List extends PureComponent {
|
||||
static propTypes = {
|
||||
canCreatePrivateChannels: PropTypes.bool.isRequired,
|
||||
@@ -38,11 +32,11 @@ export default class List extends PureComponent {
|
||||
privateChannelIds: PropTypes.array.isRequired,
|
||||
styles: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
unreadChannelIds: PropTypes.array.isRequired,
|
||||
unreadChannelIds: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
intl: intlShape
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -51,7 +45,7 @@ export default class List extends PureComponent {
|
||||
this.state = {
|
||||
sections: this.buildSections(props),
|
||||
showIndicator: false,
|
||||
width: 0,
|
||||
width: 0
|
||||
};
|
||||
|
||||
MaterialIcon.getImageSource('close', 20, this.props.theme.sidebarHeaderTextColor).then((source) => {
|
||||
@@ -66,7 +60,7 @@ export default class List extends PureComponent {
|
||||
favoriteChannelIds,
|
||||
publicChannelIds,
|
||||
privateChannelIds,
|
||||
unreadChannelIds,
|
||||
unreadChannelIds
|
||||
} = this.props;
|
||||
|
||||
if (nextProps.canCreatePrivateChannels !== canCreatePrivateChannels ||
|
||||
@@ -93,7 +87,7 @@ export default class List extends PureComponent {
|
||||
favoriteChannelIds,
|
||||
publicChannelIds,
|
||||
privateChannelIds,
|
||||
unreadChannelIds,
|
||||
unreadChannelIds
|
||||
} = props;
|
||||
const sections = [];
|
||||
|
||||
@@ -104,7 +98,7 @@ export default class List extends PureComponent {
|
||||
data: unreadChannelIds,
|
||||
renderItem: this.renderUnreadItem,
|
||||
topSeparator: false,
|
||||
bottomSeparator: true,
|
||||
bottomSeparator: true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -114,7 +108,7 @@ export default class List extends PureComponent {
|
||||
defaultMessage: 'FAVORITES',
|
||||
data: favoriteChannelIds,
|
||||
topSeparator: unreadChannelIds.length > 0,
|
||||
bottomSeparator: true,
|
||||
bottomSeparator: true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -124,7 +118,7 @@ export default class List extends PureComponent {
|
||||
defaultMessage: 'PUBLIC CHANNELS',
|
||||
data: publicChannelIds,
|
||||
topSeparator: favoriteChannelIds.length > 0 || unreadChannelIds.length > 0,
|
||||
bottomSeparator: publicChannelIds.length > 0,
|
||||
bottomSeparator: publicChannelIds.length > 0
|
||||
});
|
||||
|
||||
sections.push({
|
||||
@@ -133,7 +127,7 @@ export default class List extends PureComponent {
|
||||
defaultMessage: 'PRIVATE CHANNELS',
|
||||
data: privateChannelIds,
|
||||
topSeparator: true,
|
||||
bottomSeparator: privateChannelIds.length > 0,
|
||||
bottomSeparator: privateChannelIds.length > 0
|
||||
});
|
||||
|
||||
sections.push({
|
||||
@@ -142,13 +136,13 @@ export default class List extends PureComponent {
|
||||
defaultMessage: 'DIRECT MESSAGES',
|
||||
data: directChannelIds,
|
||||
topSeparator: true,
|
||||
bottomSeparator: directChannelIds.length > 0,
|
||||
bottomSeparator: directChannelIds.length > 0
|
||||
});
|
||||
|
||||
return sections;
|
||||
};
|
||||
|
||||
goToCreatePrivateChannel = preventDoubleTap(() => {
|
||||
goToCreatePrivateChannel = wrapWithPreventDoubleTap(() => {
|
||||
const {navigator, theme} = this.props;
|
||||
const {intl} = this.context;
|
||||
|
||||
@@ -162,16 +156,16 @@ export default class List extends PureComponent {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
passProps: {
|
||||
channelType: General.PRIVATE_CHANNEL,
|
||||
closeButton: this.closeButton,
|
||||
},
|
||||
closeButton: this.closeButton
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
goToDirectMessages = preventDoubleTap(() => {
|
||||
goToDirectMessages = wrapWithPreventDoubleTap(() => {
|
||||
const {navigator, theme} = this.props;
|
||||
const {intl} = this.context;
|
||||
|
||||
@@ -185,18 +179,18 @@ export default class List extends PureComponent {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'close-dms',
|
||||
icon: this.closeButton,
|
||||
}],
|
||||
},
|
||||
icon: this.closeButton
|
||||
}]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
goToMoreChannels = preventDoubleTap(() => {
|
||||
goToMoreChannels = wrapWithPreventDoubleTap(() => {
|
||||
const {navigator, theme} = this.props;
|
||||
const {intl} = this.context;
|
||||
|
||||
@@ -210,11 +204,11 @@ export default class List extends PureComponent {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
passProps: {
|
||||
closeButton: this.closeButton,
|
||||
},
|
||||
closeButton: this.closeButton
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -282,7 +276,7 @@ export default class List extends PureComponent {
|
||||
bottomSeparator,
|
||||
defaultMessage,
|
||||
id,
|
||||
topSeparator,
|
||||
topSeparator
|
||||
} = section;
|
||||
|
||||
return (
|
||||
@@ -304,7 +298,7 @@ export default class List extends PureComponent {
|
||||
this.refs.list._wrapperListRef.getListRef().scrollToOffset({ //eslint-disable-line no-underscore-dangle
|
||||
x: 0,
|
||||
y: 0,
|
||||
animated: true,
|
||||
animated: true
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -346,7 +340,10 @@ export default class List extends PureComponent {
|
||||
keyboardDismissMode='on-drag'
|
||||
maxToRenderPerBatch={10}
|
||||
stickySectionHeadersEnabled={false}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
viewabilityConfig={{
|
||||
viewAreaCoveragePercentThreshold: 3,
|
||||
waitForInteraction: true
|
||||
}}
|
||||
/>
|
||||
<UnreadIndicator
|
||||
show={showIndicator}
|
||||
|
||||
@@ -13,9 +13,10 @@ function mapStateToProps(state) {
|
||||
|
||||
return {
|
||||
currentTeamId: team.id,
|
||||
displayName: team.display_name,
|
||||
mentionCount: getChannelDrawerBadgeCount(state),
|
||||
teamsCount: getMyTeamsCount(state),
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,38 +4,39 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import Badge from 'app/components/badge';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import TeamIcon from 'app/components/team_icon';
|
||||
|
||||
export default class SwitchTeamsButton extends React.PureComponent {
|
||||
static propTypes = {
|
||||
currentTeamId: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
searching: PropTypes.bool.isRequired,
|
||||
onShowTeams: PropTypes.func.isRequired,
|
||||
mentionCount: PropTypes.number.isRequired,
|
||||
teamsCount: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
showTeams = preventDoubleTap(() => {
|
||||
showTeams = wrapWithPreventDoubleTap(() => {
|
||||
this.props.onShowTeams();
|
||||
});
|
||||
|
||||
render() {
|
||||
const {
|
||||
currentTeamId,
|
||||
displayName,
|
||||
mentionCount,
|
||||
searching,
|
||||
teamsCount,
|
||||
theme,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
if (!currentTeamId) {
|
||||
@@ -68,14 +69,12 @@ export default class SwitchTeamsButton extends React.PureComponent {
|
||||
<AwesomeIcon
|
||||
name='chevron-left'
|
||||
size={12}
|
||||
style={styles.switcherArrow}
|
||||
color={theme.sidebarHeaderBg}
|
||||
/>
|
||||
<View style={styles.switcherDivider}/>
|
||||
<TeamIcon
|
||||
teamId={currentTeamId}
|
||||
styleContainer={styles.teamIconContainer}
|
||||
styleText={styles.teamIconText}
|
||||
/>
|
||||
<Text style={styles.switcherTeam}>
|
||||
{displayName.substr(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
{badge}
|
||||
@@ -87,33 +86,26 @@ export default class SwitchTeamsButton extends React.PureComponent {
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
switcherContainer: {
|
||||
backgroundColor: theme.sidebarHeaderTextColor,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: 32,
|
||||
backgroundColor: theme.sidebarHeaderTextColor,
|
||||
borderRadius: 2,
|
||||
flexDirection: 'row',
|
||||
height: 32,
|
||||
justifyContent: 'center',
|
||||
marginLeft: 6,
|
||||
marginRight: 6,
|
||||
paddingHorizontal: 3,
|
||||
},
|
||||
switcherArrow: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
marginRight: 3,
|
||||
marginRight: 5,
|
||||
paddingHorizontal: 6
|
||||
},
|
||||
switcherDivider: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
height: 15,
|
||||
marginHorizontal: 6,
|
||||
width: 1,
|
||||
width: 1
|
||||
},
|
||||
teamIconContainer: {
|
||||
width: 26,
|
||||
height: 26,
|
||||
marginLeft: 3,
|
||||
},
|
||||
teamIconText: {
|
||||
fontSize: 14,
|
||||
switcherTeam: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
fontFamily: 'OpenSans',
|
||||
fontSize: 14
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: theme.mentionBj,
|
||||
@@ -124,11 +116,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
padding: 3,
|
||||
position: 'absolute',
|
||||
left: -5,
|
||||
top: -5,
|
||||
top: -5
|
||||
},
|
||||
mention: {
|
||||
color: theme.mentionColor,
|
||||
fontSize: 10,
|
||||
},
|
||||
fontSize: 10
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
|
||||
import {
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ViewPropTypes,
|
||||
ViewPropTypes
|
||||
} from 'react-native';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
@@ -18,11 +18,11 @@ export default class UnreadIndicator extends PureComponent {
|
||||
show: PropTypes.bool,
|
||||
style: ViewPropTypes.style,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onPress: () => true,
|
||||
onPress: () => true
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -65,7 +65,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
position: 'absolute',
|
||||
borderRadius: 15,
|
||||
marginHorizontal: 15,
|
||||
height: 25,
|
||||
height: 25
|
||||
},
|
||||
indicatorText: {
|
||||
backgroundColor: 'transparent',
|
||||
@@ -74,11 +74,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
textAlign: 'center',
|
||||
textAlignVertical: 'center',
|
||||
textAlignVertical: 'center'
|
||||
},
|
||||
arrow: {
|
||||
position: 'relative',
|
||||
bottom: -1,
|
||||
},
|
||||
bottom: -1
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -16,12 +16,12 @@ export default class DrawerSwiper extends Component {
|
||||
onPageSelected: PropTypes.func,
|
||||
openDrawerOffset: PropTypes.number,
|
||||
showTeams: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onPageSelected: () => true,
|
||||
openDrawerOffset: 0,
|
||||
openDrawerOffset: 0
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
@@ -64,7 +64,7 @@ export default class DrawerSwiper extends Component {
|
||||
deviceWidth,
|
||||
openDrawerOffset,
|
||||
showTeams,
|
||||
theme,
|
||||
theme
|
||||
} = this.props;
|
||||
|
||||
const initialPage = React.Children.count(children) - 1;
|
||||
@@ -92,6 +92,6 @@ export default class DrawerSwiper extends Component {
|
||||
const style = StyleSheet.create({
|
||||
pagination: {
|
||||
bottom: 0,
|
||||
position: 'absolute',
|
||||
},
|
||||
position: 'absolute'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ import DraweSwiper from './drawer_swiper';
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
...getDimensions(state),
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ function mapStateToProps(state) {
|
||||
isLandscape: isLandscape(state),
|
||||
isTablet: isTablet(state),
|
||||
teamsCount: getMyTeamsCount(state),
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ function mapDispatchToProps(dispatch) {
|
||||
makeDirectChannel,
|
||||
markChannelAsRead,
|
||||
setChannelDisplayName,
|
||||
setChannelLoading,
|
||||
}, dispatch),
|
||||
setChannelLoading
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(ChannelDrawer);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelDrawer);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getCurrentTeamId, getMySortedTeamIds} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentTeamId, getJoinableTeamIds, getMySortedTeamIds} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {handleTeamChange} from 'app/actions/views/select_team';
|
||||
@@ -18,18 +18,19 @@ function mapStateToProps(state) {
|
||||
const locale = getCurrentLocale(state);
|
||||
|
||||
return {
|
||||
canJoinOtherTeams: getJoinableTeamIds(state).length > 0,
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentUrl: removeProtocol(getCurrentUrl(state)),
|
||||
teamIds: getMySortedTeamIds(state, locale),
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
handleTeamChange,
|
||||
}, dispatch),
|
||||
handleTeamChange
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,40 +6,33 @@ import PropTypes from 'prop-types';
|
||||
import {
|
||||
FlatList,
|
||||
Platform,
|
||||
StatusBar,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {ListTypes, ViewTypes} from 'app/constants';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import tracker from 'app/utils/time_tracker';
|
||||
|
||||
import TeamsListItem from './teams_list_item';
|
||||
|
||||
const {ANDROID_TOP_PORTRAIT} = ViewTypes;
|
||||
const VIEWABILITY_CONFIG = {
|
||||
...ListTypes.VISIBILITY_CONFIG_DEFAULTS,
|
||||
waitForInteraction: true,
|
||||
};
|
||||
|
||||
class TeamsList extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
handleTeamChange: PropTypes.func.isRequired,
|
||||
handleTeamChange: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
canJoinOtherTeams: PropTypes.bool.isRequired,
|
||||
closeChannelDrawer: PropTypes.func.isRequired,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
currentUrl: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
teamIds: PropTypes.array.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -51,7 +44,6 @@ class TeamsList extends PureComponent {
|
||||
}
|
||||
|
||||
selectTeam = (teamId) => {
|
||||
StatusBar.setHidden(false, 'slide');
|
||||
requestAnimationFrame(() => {
|
||||
const {actions, closeChannelDrawer, currentTeamId} = this.props;
|
||||
if (teamId !== currentTeamId) {
|
||||
@@ -63,7 +55,7 @@ class TeamsList extends PureComponent {
|
||||
});
|
||||
};
|
||||
|
||||
goToSelectTeam = preventDoubleTap(() => {
|
||||
goToSelectTeam = wrapWithPreventDoubleTap(() => {
|
||||
const {currentUrl, intl, navigator, theme} = this.props;
|
||||
|
||||
navigator.showModal({
|
||||
@@ -76,18 +68,18 @@ class TeamsList extends PureComponent {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
},
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'close-teams',
|
||||
icon: this.closeButton,
|
||||
}],
|
||||
icon: this.closeButton
|
||||
}]
|
||||
},
|
||||
passProps: {
|
||||
currentUrl,
|
||||
theme,
|
||||
},
|
||||
theme
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -105,22 +97,25 @@ class TeamsList extends PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {teamIds, theme} = this.props;
|
||||
const {canJoinOtherTeams, teamIds, theme} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const moreAction = (
|
||||
<TouchableHighlight
|
||||
style={styles.moreActionContainer}
|
||||
onPress={this.goToSelectTeam}
|
||||
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
|
||||
>
|
||||
<Text
|
||||
style={styles.moreAction}
|
||||
let moreAction;
|
||||
if (canJoinOtherTeams) {
|
||||
moreAction = (
|
||||
<TouchableHighlight
|
||||
style={styles.moreActionContainer}
|
||||
onPress={this.goToSelectTeam}
|
||||
underlayColor={changeOpacity(theme.sidebarHeaderBg, 0.5)}
|
||||
>
|
||||
{'+'}
|
||||
</Text>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
<Text
|
||||
style={styles.moreAction}
|
||||
>
|
||||
{'+'}
|
||||
</Text>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@@ -138,7 +133,10 @@ class TeamsList extends PureComponent {
|
||||
data={teamIds}
|
||||
renderItem={this.renderItem}
|
||||
keyExtractor={this.keyExtractor}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
viewabilityConfig={{
|
||||
viewAreaCoveragePercentThreshold: 3,
|
||||
waitForInteraction: false
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -149,10 +147,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.sidebarBg,
|
||||
flex: 1,
|
||||
flex: 1
|
||||
},
|
||||
statusBar: {
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
backgroundColor: theme.sidebarHeaderBg
|
||||
},
|
||||
headerContainer: {
|
||||
alignItems: 'center',
|
||||
@@ -162,19 +160,19 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
borderBottomColor: changeOpacity(theme.sidebarHeaderTextColor, 0.10),
|
||||
...Platform.select({
|
||||
android: {
|
||||
height: ANDROID_TOP_PORTRAIT,
|
||||
height: 46
|
||||
},
|
||||
ios: {
|
||||
height: 44,
|
||||
},
|
||||
}),
|
||||
height: 44
|
||||
}
|
||||
})
|
||||
},
|
||||
header: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
flex: 1,
|
||||
fontSize: 17,
|
||||
textAlign: 'center',
|
||||
fontWeight: '600',
|
||||
fontWeight: '600'
|
||||
},
|
||||
moreActionContainer: {
|
||||
alignItems: 'center',
|
||||
@@ -182,17 +180,17 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
width: 50,
|
||||
...Platform.select({
|
||||
android: {
|
||||
height: ANDROID_TOP_PORTRAIT,
|
||||
height: 46
|
||||
},
|
||||
ios: {
|
||||
height: 44,
|
||||
},
|
||||
}),
|
||||
height: 44
|
||||
}
|
||||
})
|
||||
},
|
||||
moreAction: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 30,
|
||||
},
|
||||
fontSize: 30
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ function makeMapStateToProps() {
|
||||
displayName: team.display_name,
|
||||
mentionCount: getMentionCount(state, ownProps.teamId),
|
||||
name: team.name,
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,14 +6,12 @@ import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||
|
||||
import Badge from 'app/components/badge';
|
||||
import TeamIcon from 'app/components/team_icon';
|
||||
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
export default class TeamsListItem extends React.PureComponent {
|
||||
@@ -25,10 +23,10 @@ export default class TeamsListItem extends React.PureComponent {
|
||||
name: PropTypes.string.isRequired,
|
||||
selectTeam: PropTypes.func.isRequired,
|
||||
teamId: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
selectTeam = preventDoubleTap(() => {
|
||||
selectTeam = wrapWithPreventDoubleTap(() => {
|
||||
this.props.selectTeam(this.props.teamId);
|
||||
});
|
||||
|
||||
@@ -40,7 +38,7 @@ export default class TeamsListItem extends React.PureComponent {
|
||||
mentionCount,
|
||||
name,
|
||||
teamId,
|
||||
theme,
|
||||
theme
|
||||
} = this.props;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
@@ -73,11 +71,11 @@ export default class TeamsListItem extends React.PureComponent {
|
||||
onPress={this.selectTeam}
|
||||
>
|
||||
<View style={styles.teamContainer}>
|
||||
<TeamIcon
|
||||
teamId={teamId}
|
||||
styleContainer={styles.teamIconContainer}
|
||||
styleText={styles.teamIconText}
|
||||
/>
|
||||
<View style={styles.teamIconContainer}>
|
||||
<Text style={styles.teamIcon}>
|
||||
{displayName.substr(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.teamNameContainer}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
@@ -106,40 +104,47 @@ export default class TeamsListItem extends React.PureComponent {
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
teamWrapper: {
|
||||
marginTop: 20,
|
||||
marginTop: 20
|
||||
},
|
||||
teamContainer: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
marginHorizontal: 16,
|
||||
marginHorizontal: 16
|
||||
},
|
||||
teamIconContainer: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.sidebarText,
|
||||
borderRadius: 2,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
width: 40
|
||||
},
|
||||
teamIcon: {
|
||||
color: theme.sidebarBg,
|
||||
fontFamily: 'OpenSans',
|
||||
fontSize: 18,
|
||||
fontWeight: '600'
|
||||
},
|
||||
teamNameContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
marginLeft: 10,
|
||||
marginLeft: 10
|
||||
},
|
||||
teamName: {
|
||||
color: theme.sidebarText,
|
||||
fontSize: 18,
|
||||
},
|
||||
teamIconContainer: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
},
|
||||
teamIconText: {
|
||||
fontSize: 18,
|
||||
fontSize: 18
|
||||
},
|
||||
teamUrl: {
|
||||
color: changeOpacity(theme.sidebarText, 0.5),
|
||||
fontSize: 12,
|
||||
fontSize: 12
|
||||
},
|
||||
checkmarkContainer: {
|
||||
alignItems: 'flex-end',
|
||||
alignItems: 'flex-end'
|
||||
},
|
||||
checkmark: {
|
||||
color: theme.sidebarText,
|
||||
fontSize: 20,
|
||||
fontSize: 20
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: theme.mentionBj,
|
||||
@@ -150,11 +155,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
padding: 3,
|
||||
position: 'absolute',
|
||||
left: 45,
|
||||
top: -7.5,
|
||||
top: -7.5
|
||||
},
|
||||
mention: {
|
||||
color: theme.mentionColor,
|
||||
fontSize: 10,
|
||||
},
|
||||
fontSize: 10
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -5,17 +5,11 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import {
|
||||
ArchiveIcon,
|
||||
AwayAvatar,
|
||||
DndAvatar,
|
||||
OfflineAvatar,
|
||||
OnlineAvatar,
|
||||
} from 'app/components/status_icons';
|
||||
import {AwayAvatar, DndAvatar, OfflineAvatar, OnlineAvatar} from 'app/components/status_icons';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
||||
@@ -29,30 +23,19 @@ export default class ChannelIcon extends React.PureComponent {
|
||||
membersCount: PropTypes.number,
|
||||
size: PropTypes.number,
|
||||
status: PropTypes.string,
|
||||
teammateDeletedAt: PropTypes.number,
|
||||
theme: PropTypes.object.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isActive: false,
|
||||
isInfo: false,
|
||||
isUnread: false,
|
||||
size: 12,
|
||||
size: 12
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
isActive,
|
||||
isUnread,
|
||||
isInfo,
|
||||
membersCount,
|
||||
size,
|
||||
status,
|
||||
teammateDeletedAt,
|
||||
theme,
|
||||
type,
|
||||
} = this.props;
|
||||
const {isActive, isUnread, isInfo, membersCount, size, status, theme, type} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let activeIcon;
|
||||
@@ -106,14 +89,6 @@ export default class ChannelIcon extends React.PureComponent {
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (type === General.DM_CHANNEL && teammateDeletedAt) {
|
||||
icon = (
|
||||
<ArchiveIcon
|
||||
width={size}
|
||||
height={size}
|
||||
color={offlineColor}
|
||||
/>
|
||||
);
|
||||
} else if (type === General.DM_CHANNEL) {
|
||||
switch (status) {
|
||||
case General.AWAY:
|
||||
@@ -167,49 +142,49 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
marginRight: 12,
|
||||
alignItems: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
icon: {
|
||||
color: changeOpacity(theme.sidebarText, 0.4),
|
||||
color: changeOpacity(theme.sidebarText, 0.4)
|
||||
},
|
||||
iconActive: {
|
||||
color: theme.sidebarTextActiveColor,
|
||||
color: theme.sidebarTextActiveColor
|
||||
},
|
||||
iconUnread: {
|
||||
color: theme.sidebarUnreadText,
|
||||
color: theme.sidebarUnreadText
|
||||
},
|
||||
iconInfo: {
|
||||
color: theme.centerChannelColor,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
groupBox: {
|
||||
alignSelf: 'flex-start',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: changeOpacity(theme.sidebarText, 0.4),
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
groupBoxActive: {
|
||||
borderColor: theme.sidebarTextActiveColor,
|
||||
borderColor: theme.sidebarTextActiveColor
|
||||
},
|
||||
groupBoxUnread: {
|
||||
borderColor: theme.sidebarUnreadText,
|
||||
borderColor: theme.sidebarUnreadText
|
||||
},
|
||||
groupBoxInfo: {
|
||||
borderColor: theme.centerChannelColor,
|
||||
borderColor: theme.centerChannelColor
|
||||
},
|
||||
group: {
|
||||
color: changeOpacity(theme.sidebarText, 0.4),
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
fontWeight: '600'
|
||||
},
|
||||
groupActive: {
|
||||
color: theme.sidebarTextActiveColor,
|
||||
color: theme.sidebarTextActiveColor
|
||||
},
|
||||
groupUnread: {
|
||||
color: theme.sidebarUnreadText,
|
||||
color: theme.sidebarUnreadText
|
||||
},
|
||||
groupInfo: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
color: theme.centerChannelColor
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Platform,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {getFullName} from 'mattermost-redux/utils/user_utils';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
@@ -26,32 +25,27 @@ class ChannelIntro extends PureComponent {
|
||||
intl: intlShape.isRequired,
|
||||
isLoadingPosts: PropTypes.bool,
|
||||
navigator: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
goToUserProfile = (userId) => {
|
||||
const {intl, navigator, theme} = this.props;
|
||||
const options = {
|
||||
|
||||
navigator.push({
|
||||
screen: 'UserProfile',
|
||||
title: intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'}),
|
||||
animated: true,
|
||||
backButtonTitle: '',
|
||||
passProps: {
|
||||
userId,
|
||||
userId
|
||||
},
|
||||
navigatorStyle: {
|
||||
navBarTextColor: theme.sidebarHeaderTextColor,
|
||||
navBarBackgroundColor: theme.sidebarHeaderBg,
|
||||
navBarButtonColor: theme.sidebarHeaderTextColor,
|
||||
screenBackgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
};
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
navigator.push(options);
|
||||
} else {
|
||||
navigator.showModal(options);
|
||||
}
|
||||
screenBackgroundColor: theme.centerChannelBg
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getDisplayName = (member) => {
|
||||
@@ -75,7 +69,7 @@ class ChannelIntro extends PureComponent {
|
||||
return currentChannelMembers.map((member) => (
|
||||
<TouchableOpacity
|
||||
key={member.id}
|
||||
onPress={preventDoubleTap(() => this.goToUserProfile(member.id))}
|
||||
onPress={() => preventDoubleTap(this.goToUserProfile, this, member.id)}
|
||||
style={style.profile}
|
||||
>
|
||||
<ProfilePicture
|
||||
@@ -95,7 +89,7 @@ class ChannelIntro extends PureComponent {
|
||||
return currentChannelMembers.map((member, index) => (
|
||||
<TouchableOpacity
|
||||
key={member.id}
|
||||
onPress={preventDoubleTap(() => this.goToUserProfile(member.id))}
|
||||
onPress={() => preventDoubleTap(this.goToUserProfile, this, member.id)}
|
||||
>
|
||||
<Text style={style.displayName}>
|
||||
{index === currentChannelMembers.length - 1 ? this.getDisplayName(member) : `${this.getDisplayName(member)}, `}
|
||||
@@ -115,9 +109,9 @@ class ChannelIntro extends PureComponent {
|
||||
<Text style={style.message}>
|
||||
{intl.formatMessage({
|
||||
id: 'mobile.intro_messages.DM',
|
||||
defaultMessage: 'This is the start of your direct message history with {teammate}. Direct messages and files shared here are not shown to people outside this area.',
|
||||
defaultMessage: 'This is the start of your direct message history with {teammate}. Direct messages and files shared here are not shown to people outside this area.'
|
||||
}, {
|
||||
teammate,
|
||||
teammate
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
@@ -134,7 +128,7 @@ class ChannelIntro extends PureComponent {
|
||||
<Text style={style.message}>
|
||||
{intl.formatMessage({
|
||||
id: 'intro_messages.group_message',
|
||||
defaultMessage: 'This is the start of your group message history with these teammates. Messages and files shared here are not shown to people outside this area.',
|
||||
defaultMessage: 'This is the start of your group message history with these teammates. Messages and files shared here are not shown to people outside this area.'
|
||||
})}
|
||||
</Text>
|
||||
);
|
||||
@@ -147,7 +141,7 @@ class ChannelIntro extends PureComponent {
|
||||
const date = intl.formatDate(currentChannel.create_at, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
day: 'numeric'
|
||||
});
|
||||
|
||||
let mainMessageIntl;
|
||||
@@ -162,9 +156,9 @@ class ChannelIntro extends PureComponent {
|
||||
date,
|
||||
type: intl.formatMessage({
|
||||
id: 'intro_messages.channel',
|
||||
defaultMessage: 'channel',
|
||||
}),
|
||||
},
|
||||
defaultMessage: 'channel'
|
||||
})
|
||||
}
|
||||
};
|
||||
} else {
|
||||
mainMessageIntl = {
|
||||
@@ -175,20 +169,20 @@ class ChannelIntro extends PureComponent {
|
||||
date,
|
||||
type: intl.formatMessage({
|
||||
id: 'intro_messages.channel',
|
||||
defaultMessage: 'channel',
|
||||
}),
|
||||
},
|
||||
defaultMessage: 'channel'
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const mainMessage = intl.formatMessage({
|
||||
id: mainMessageIntl.id,
|
||||
defaultMessage: mainMessageIntl.defaultMessage,
|
||||
defaultMessage: mainMessageIntl.defaultMessage
|
||||
}, mainMessageIntl.values);
|
||||
|
||||
const anyMemberMessage = intl.formatMessage({
|
||||
id: 'intro_messages.anyMember',
|
||||
defaultMessage: ' Any member can join and read this channel.',
|
||||
defaultMessage: ' Any member can join and read this channel.'
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -196,9 +190,9 @@ class ChannelIntro extends PureComponent {
|
||||
<Text style={style.channelTitle}>
|
||||
{intl.formatMessage({
|
||||
id: 'intro_messages.beginning',
|
||||
defaultMessage: 'Beginning of {name}',
|
||||
defaultMessage: 'Beginning of {name}'
|
||||
}, {
|
||||
name: currentChannel.display_name,
|
||||
name: currentChannel.display_name
|
||||
})}
|
||||
</Text>
|
||||
<Text style={style.message}>
|
||||
@@ -216,25 +210,25 @@ class ChannelIntro extends PureComponent {
|
||||
const date = intl.formatDate(currentChannel.create_at, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
day: 'numeric'
|
||||
});
|
||||
|
||||
const mainMessage = intl.formatMessage({
|
||||
id: 'intro_messages.creator',
|
||||
defaultMessage: 'This is the start of the {name} {type}, created by {creator} on {date}.',
|
||||
defaultMessage: 'This is the start of the {name} {type}, created by {creator} on {date}.'
|
||||
}, {
|
||||
name: currentChannel.display_name,
|
||||
creator: creatorName,
|
||||
date,
|
||||
type: intl.formatMessage({
|
||||
id: 'intro_messages.group',
|
||||
defaultMessage: 'private channel',
|
||||
}),
|
||||
defaultMessage: 'private channel'
|
||||
})
|
||||
});
|
||||
|
||||
const onlyInvitedMessage = intl.formatMessage({
|
||||
id: 'intro_messages.onlyInvited',
|
||||
defaultMessage: ' Only invited members can see this private channel.',
|
||||
defaultMessage: ' Only invited members can see this private channel.'
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -242,9 +236,9 @@ class ChannelIntro extends PureComponent {
|
||||
<Text style={style.channelTitle}>
|
||||
{intl.formatMessage({
|
||||
id: 'intro_messages.beginning',
|
||||
defaultMessage: 'Beginning of {name}',
|
||||
defaultMessage: 'Beginning of {name}'
|
||||
}, {
|
||||
name: currentChannel.display_name,
|
||||
name: currentChannel.display_name
|
||||
})}
|
||||
</Text>
|
||||
<Text style={style.message}>
|
||||
@@ -263,23 +257,23 @@ class ChannelIntro extends PureComponent {
|
||||
<Text style={style.channelTitle}>
|
||||
{intl.formatMessage({
|
||||
id: 'intro_messages.beginning',
|
||||
defaultMessage: 'Beginning of {name}',
|
||||
defaultMessage: 'Beginning of {name}'
|
||||
}, {
|
||||
name: currentChannel.display_name,
|
||||
name: currentChannel.display_name
|
||||
})}
|
||||
</Text>
|
||||
<Text style={style.channelWelcome}>
|
||||
{intl.formatMessage({
|
||||
id: 'mobile.intro_messages.default_welcome',
|
||||
defaultMessage: 'Welcome to {name}!',
|
||||
defaultMessage: 'Welcome to {name}!'
|
||||
}, {
|
||||
name: currentChannel.display_name,
|
||||
name: currentChannel.display_name
|
||||
})}
|
||||
</Text>
|
||||
<Text style={style.message}>
|
||||
{intl.formatMessage({
|
||||
id: 'mobile.intro_messages.default_message',
|
||||
defaultMessage: 'This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.',
|
||||
defaultMessage: 'This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -354,42 +348,42 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 19,
|
||||
fontWeight: '600',
|
||||
marginBottom: 12,
|
||||
marginBottom: 12
|
||||
},
|
||||
channelWelcome: {
|
||||
color: theme.centerChannelColor,
|
||||
marginBottom: 12,
|
||||
marginBottom: 12
|
||||
},
|
||||
container: {
|
||||
marginTop: 60,
|
||||
marginHorizontal: 12,
|
||||
marginBottom: 12,
|
||||
marginBottom: 12
|
||||
},
|
||||
displayName: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
fontWeight: '600'
|
||||
},
|
||||
message: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.8),
|
||||
fontSize: 15,
|
||||
lineHeight: 22,
|
||||
lineHeight: 22
|
||||
},
|
||||
namesContainer: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
marginBottom: 12,
|
||||
marginBottom: 12
|
||||
},
|
||||
profile: {
|
||||
height: 67,
|
||||
marginBottom: 12,
|
||||
marginRight: 12,
|
||||
marginRight: 12
|
||||
},
|
||||
profilesContainer: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -10,14 +10,24 @@ import {getCurrentUserId, getUser, makeGetProfilesInChannel} from 'mattermost-re
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {getChannelMembersForDm} from 'app/selectors/channel';
|
||||
|
||||
import ChannelIntro from './channel_intro';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const getChannel = makeGetChannel();
|
||||
const getProfilesInChannel = makeGetProfilesInChannel();
|
||||
|
||||
const getOtherUserIdForDm = createSelector(
|
||||
(state, channel) => channel,
|
||||
getCurrentUserId,
|
||||
(channel, currentUserId) => {
|
||||
if (!channel) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return channel.name.split('__').find((m) => m !== currentUserId);
|
||||
}
|
||||
);
|
||||
|
||||
const getChannelMembers = createSelector(
|
||||
getCurrentUserId,
|
||||
(state, channel) => getProfilesInChannel(state, channel.id),
|
||||
@@ -27,6 +37,17 @@ function makeMapStateToProps() {
|
||||
}
|
||||
);
|
||||
|
||||
const getChannelMembersForDm = createSelector(
|
||||
(state, channel) => getUser(state, getOtherUserIdForDm(state, channel)),
|
||||
(otherUser) => {
|
||||
if (!otherUser) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [otherUser];
|
||||
}
|
||||
);
|
||||
|
||||
return function mapStateToProps(state, ownProps) {
|
||||
const currentChannel = getChannel(state, {id: ownProps.channelId}) || {};
|
||||
const {status: getPostsRequestStatus} = state.requests.posts.getPosts;
|
||||
@@ -51,7 +72,7 @@ function makeMapStateToProps() {
|
||||
currentChannel,
|
||||
currentChannelMembers,
|
||||
isLoadingPosts: (!postsInChannel || postsInChannel.length === 0) && getPostsRequestStatus === RequestStatus.STARTED,
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,22 +15,22 @@ export default class ChannelLink extends React.PureComponent {
|
||||
channelsByName: PropTypes.object.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
handleSelectChannel: PropTypes.func.isRequired,
|
||||
setChannelDisplayName: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
setChannelDisplayName: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
channel: this.getChannelFromChannelName(props),
|
||||
channel: this.getChannelFromChannelName(props)
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.channelName !== this.props.channelName || nextProps.channelsByName !== this.props.channelsByName) {
|
||||
this.setState({
|
||||
channel: this.getChannelFromChannelName(nextProps),
|
||||
channel: this.getChannelFromChannelName(nextProps)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export default class ChannelLink extends React.PureComponent {
|
||||
const channel = this.state.channel;
|
||||
|
||||
if (!channel) {
|
||||
return <Text style={this.props.textStyle}>{`~${this.props.channelName}`}</Text>;
|
||||
return <Text style={this.props.textStyle}>{'~' + this.props.channelName}</Text>;
|
||||
}
|
||||
|
||||
const suffix = this.props.channelName.substring(channel.name.length);
|
||||
@@ -74,7 +74,7 @@ export default class ChannelLink extends React.PureComponent {
|
||||
style={this.props.linkStyle}
|
||||
onPress={this.handlePress}
|
||||
>
|
||||
{`~${channel.display_name}`}
|
||||
{channel.display_name}
|
||||
</Text>
|
||||
{suffix}
|
||||
</Text>
|
||||
|
||||
@@ -12,7 +12,7 @@ import ChannelLink from './channel_link';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
channelsByName: getChannelsNameMapInCurrentTeam(state),
|
||||
channelsByName: getChannelsNameMapInCurrentTeam(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
handleSelectChannel,
|
||||
setChannelDisplayName,
|
||||
}, dispatch),
|
||||
setChannelDisplayName
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Platform,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
|
||||
@@ -19,7 +19,7 @@ export default class ChannelLoader extends PureComponent {
|
||||
static propTypes = {
|
||||
channelIsLoading: PropTypes.bool.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
buildSections(key, style, top) {
|
||||
@@ -36,7 +36,7 @@ export default class ChannelLoader extends PureComponent {
|
||||
colors={[
|
||||
changeOpacity('#e5e5e4', GRADIENT_START),
|
||||
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END)
|
||||
]}
|
||||
locations={[0.1, 0.3, 0.7]}
|
||||
style={[style.messageText, {width: 106}]}
|
||||
@@ -47,7 +47,7 @@ export default class ChannelLoader extends PureComponent {
|
||||
colors={[
|
||||
changeOpacity('#e5e5e4', GRADIENT_START),
|
||||
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END)
|
||||
]}
|
||||
locations={[0.1, 0.3, 0.7]}
|
||||
style={[style.messageText, {alignSelf: 'stretch'}]}
|
||||
@@ -58,7 +58,7 @@ export default class ChannelLoader extends PureComponent {
|
||||
colors={[
|
||||
changeOpacity('#e5e5e4', GRADIENT_START),
|
||||
changeOpacity('#d6d6d5', GRADIENT_MIDDLE),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END),
|
||||
changeOpacity('#e5e5e4', GRADIENT_END)
|
||||
]}
|
||||
locations={[0.1, 0.3, 0.7]}
|
||||
style={[style.messageText, {alignSelf: 'stretch'}]}
|
||||
@@ -93,35 +93,35 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
position: 'absolute',
|
||||
...Platform.select({
|
||||
android: {
|
||||
top: 0,
|
||||
top: 0
|
||||
},
|
||||
ios: {
|
||||
top: 15,
|
||||
},
|
||||
}),
|
||||
top: 15
|
||||
}
|
||||
})
|
||||
},
|
||||
avatar: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderRadius: 16,
|
||||
height: 32,
|
||||
width: 32,
|
||||
width: 32
|
||||
},
|
||||
messageText: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 10,
|
||||
marginBottom: 10,
|
||||
marginBottom: 10
|
||||
},
|
||||
section: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
flexDirection: 'row',
|
||||
paddingLeft: 12,
|
||||
paddingRight: 20,
|
||||
marginVertical: 10,
|
||||
marginVertical: 10
|
||||
},
|
||||
sectionMessage: {
|
||||
marginLeft: 12,
|
||||
flex: 1,
|
||||
},
|
||||
flex: 1
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ function mapStateToProps(state) {
|
||||
return {
|
||||
channelIsLoading: state.views.channel.loading,
|
||||
deviceWidth,
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export default class CheckMark extends PureComponent {
|
||||
static propTypes = {
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
color: PropTypes.string.isRequired,
|
||||
color: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Animated,
|
||||
Linking,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
@@ -27,7 +27,7 @@ export default class ClientUpgradeListener extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
logError: PropTypes.func.isRequired,
|
||||
setLastUpgradeCheck: PropTypes.func.isRequired,
|
||||
setLastUpgradeCheck: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
currentVersion: PropTypes.string,
|
||||
downloadLink: PropTypes.string,
|
||||
@@ -37,11 +37,11 @@ export default class ClientUpgradeListener extends PureComponent {
|
||||
latestVersion: PropTypes.string,
|
||||
minVersion: PropTypes.string,
|
||||
navigator: PropTypes.object,
|
||||
theme: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
intl: intlShape
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -54,7 +54,7 @@ export default class ClientUpgradeListener extends PureComponent {
|
||||
});
|
||||
|
||||
this.state = {
|
||||
top: new Animated.Value(-100),
|
||||
top: new Animated.Value(-100)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export default class ClientUpgradeListener extends PureComponent {
|
||||
}
|
||||
Animated.timing(this.state.top, {
|
||||
toValue,
|
||||
duration: 300,
|
||||
duration: 300
|
||||
}).start();
|
||||
};
|
||||
|
||||
@@ -128,11 +128,11 @@ export default class ClientUpgradeListener extends PureComponent {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.client_upgrade.download_error.title',
|
||||
defaultMessage: 'Upgrade Error',
|
||||
defaultMessage: 'Upgrade Error'
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.client_upgrade.download_error.message',
|
||||
defaultMessage: 'An error occurred while trying to open the download link.',
|
||||
defaultMessage: 'An error occurred while trying to open the download link.'
|
||||
})
|
||||
);
|
||||
|
||||
@@ -152,17 +152,17 @@ export default class ClientUpgradeListener extends PureComponent {
|
||||
navigatorStyle: {
|
||||
navBarHidden: false,
|
||||
statusBarHidden: false,
|
||||
statusBarHideWithNavBar: false,
|
||||
statusBarHideWithNavBar: false
|
||||
},
|
||||
navigatorButtons: {
|
||||
leftButtons: [{
|
||||
id: 'close-upgrade',
|
||||
icon: this.closeButton,
|
||||
}],
|
||||
icon: this.closeButton
|
||||
}]
|
||||
},
|
||||
passProps: {
|
||||
upgradeType: this.state.upgradeType,
|
||||
},
|
||||
upgradeType: this.state.upgradeType
|
||||
}
|
||||
});
|
||||
|
||||
this.toggleUpgradeMessage(false);
|
||||
@@ -226,28 +226,28 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
justifyContent: 'space-around',
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
borderTopWidth: 1,
|
||||
borderTopWidth: 1
|
||||
},
|
||||
button: {
|
||||
color: theme.linkColor,
|
||||
fontSize: 13,
|
||||
paddingHorizontal: 5,
|
||||
paddingVertical: 5,
|
||||
paddingVertical: 5
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelBg, 0.8),
|
||||
borderRadius: 5,
|
||||
borderRadius: 5
|
||||
},
|
||||
message: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
messageText: {
|
||||
fontSize: 16,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.8),
|
||||
fontWeight: '600',
|
||||
fontWeight: '600'
|
||||
},
|
||||
wrapper: {
|
||||
position: 'absolute',
|
||||
@@ -262,10 +262,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
shadowColor: theme.centerChannelColor,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 3,
|
||||
height: 3
|
||||
},
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 2,
|
||||
},
|
||||
shadowRadius: 2
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ function mapStateToProps(state) {
|
||||
lastUpgradeCheck: state.views.clientUpgrade.lastUpdateCheck,
|
||||
latestVersion,
|
||||
minVersion,
|
||||
theme: getTheme(state),
|
||||
theme: getTheme(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
logError,
|
||||
setLastUpgradeCheck,
|
||||
}, dispatch),
|
||||
setLastUpgradeCheck
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
export default class ConditionalTouchable extends React.PureComponent {
|
||||
static propTypes = {
|
||||
touchable: PropTypes.bool,
|
||||
children: CustomPropTypes.Children.isRequired,
|
||||
children: CustomPropTypes.Children.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
@@ -5,7 +5,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
@@ -17,7 +17,7 @@ export default class ChannelListRow extends React.PureComponent {
|
||||
id: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
channel: PropTypes.object.isRequired,
|
||||
...CustomListRow.propTypes,
|
||||
...CustomListRow.propTypes
|
||||
};
|
||||
|
||||
onPress = () => {
|
||||
@@ -70,25 +70,25 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
titleContainer: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
displayName: {
|
||||
fontSize: 16,
|
||||
color: theme.centerChannelColor,
|
||||
marginLeft: 5,
|
||||
marginLeft: 5
|
||||
},
|
||||
icon: {
|
||||
fontSize: 16,
|
||||
color: theme.centerChannelColor,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
purpose: {
|
||||
marginTop: 7,
|
||||
fontSize: 13,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ function makeMapStateToProps() {
|
||||
return (state, ownProps) => {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
channel: getChannel(state, ownProps),
|
||||
channel: getChannel(state, ownProps)
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
@@ -19,11 +19,11 @@ export default class CustomListRow extends React.PureComponent {
|
||||
enabled: PropTypes.bool,
|
||||
selectable: PropTypes.bool,
|
||||
selected: PropTypes.bool,
|
||||
children: CustomPropTypes.Children,
|
||||
children: CustomPropTypes.Children
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
enabled: true,
|
||||
enabled: true
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -62,11 +62,11 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
height: 65,
|
||||
paddingHorizontal: 15,
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
displayName: {
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
selector: {
|
||||
height: 28,
|
||||
@@ -75,20 +75,20 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
borderWidth: 1,
|
||||
borderColor: '#888',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
selectorContainer: {
|
||||
height: 50,
|
||||
paddingRight: 15,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
selectorDisabled: {
|
||||
backgroundColor: '#888',
|
||||
backgroundColor: '#888'
|
||||
},
|
||||
selectorFilled: {
|
||||
backgroundColor: '#378FD2',
|
||||
borderWidth: 0,
|
||||
},
|
||||
borderWidth: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class CustomList extends PureComponent {
|
||||
onRowSelect: PropTypes.func,
|
||||
renderRow: PropTypes.func.isRequired,
|
||||
createSections: PropTypes.func,
|
||||
showNoResults: PropTypes.bool,
|
||||
showNoResults: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -40,7 +40,7 @@ export default class CustomList extends PureComponent {
|
||||
selectable: false,
|
||||
createSections: () => true,
|
||||
showSections: true,
|
||||
showNoResults: true,
|
||||
showNoResults: true
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -64,7 +64,7 @@ export default class CustomList extends PureComponent {
|
||||
const dataSource = showSections ? this.state.dataSource.cloneWithRowsAndSections(mergedData) : this.state.dataSource.cloneWithRows(mergedData);
|
||||
this.setState({
|
||||
data: mergedData,
|
||||
dataSource,
|
||||
dataSource
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ export default class CustomList extends PureComponent {
|
||||
buildDataSource = (props) => {
|
||||
const ds = new ListView.DataSource({
|
||||
rowHasChanged: (r1, r2) => r1 !== r2,
|
||||
sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
|
||||
sectionHeaderHasChanged: (s1, s2) => s1 !== s2
|
||||
});
|
||||
let newData = props.data;
|
||||
if (props.showSections) {
|
||||
@@ -81,7 +81,7 @@ export default class CustomList extends PureComponent {
|
||||
const dataSource = props.showSections ? ds.cloneWithRowsAndSections(newData) : ds.cloneWithRows(newData);
|
||||
return {
|
||||
data: newData,
|
||||
dataSource,
|
||||
dataSource
|
||||
};
|
||||
};
|
||||
|
||||
@@ -97,7 +97,7 @@ export default class CustomList extends PureComponent {
|
||||
const dataSource = this.state.dataSource.cloneWithRowsAndSections(mergedData);
|
||||
this.setState({
|
||||
data: mergedData,
|
||||
dataSource,
|
||||
dataSource
|
||||
}, () => this.props.onRowSelect(id));
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ export default class CustomList extends PureComponent {
|
||||
item,
|
||||
selected: item.selected,
|
||||
selectable: this.props.selectable,
|
||||
onPress: this.props.onRowPress,
|
||||
onPress: this.props.onRowPress
|
||||
};
|
||||
|
||||
if ('disableSelect' in item) {
|
||||
@@ -181,7 +181,7 @@ export default class CustomList extends PureComponent {
|
||||
searching,
|
||||
showNoResults,
|
||||
showSections,
|
||||
theme,
|
||||
theme
|
||||
} = this.props;
|
||||
const {dataSource} = this.state;
|
||||
const style = getStyleFromTheme(theme);
|
||||
@@ -245,45 +245,45 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
...Platform.select({
|
||||
android: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
}),
|
||||
marginBottom: 20
|
||||
}
|
||||
})
|
||||
},
|
||||
loadingText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.6),
|
||||
color: changeOpacity(theme.centerChannelColor, 0.6)
|
||||
},
|
||||
searching: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
width: '100%'
|
||||
},
|
||||
sectionContainer: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.07),
|
||||
paddingLeft: 10,
|
||||
paddingVertical: 2,
|
||||
paddingVertical: 2
|
||||
},
|
||||
sectionWrapper: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
sectionText: {
|
||||
fontWeight: '600',
|
||||
color: theme.centerChannelColor,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1)
|
||||
},
|
||||
noResultContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
noResultText: {
|
||||
fontSize: 26,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -4,16 +4,15 @@
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import UserListRow from './user_list_row';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
isMyUser: getCurrentUserId(state) === ownProps.id,
|
||||
theme: getTheme(state),
|
||||
user: getUser(state, ownProps.id),
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state),
|
||||
teammateNameDisplay: getTeammateNameDisplaySetting(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import ProfilePicture from 'app/components/profile_picture';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
@@ -18,63 +17,36 @@ import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
||||
export default class UserListRow extends React.PureComponent {
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
isMyUser: PropTypes.bool.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
user: PropTypes.object.isRequired,
|
||||
teammateNameDisplay: PropTypes.string.isRequired,
|
||||
...CustomListRow.propTypes,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape,
|
||||
...CustomListRow.propTypes
|
||||
};
|
||||
|
||||
onPress = () => {
|
||||
if (this.props.onPress) {
|
||||
this.props.onPress(this.props.id);
|
||||
}
|
||||
this.props.onPress(this.props.id);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {
|
||||
enabled,
|
||||
isMyUser,
|
||||
selectable,
|
||||
selected,
|
||||
teammateNameDisplay,
|
||||
theme,
|
||||
user,
|
||||
} = this.props;
|
||||
|
||||
const {id, username} = user;
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
let usernameDisplay = `(@${username})`;
|
||||
if (isMyUser) {
|
||||
usernameDisplay = formatMessage({
|
||||
id: 'mobile.more_dms.you',
|
||||
defaultMessage: '(@{username} - you)',
|
||||
}, {username});
|
||||
}
|
||||
const style = getStyleFromTheme(this.props.theme);
|
||||
|
||||
return (
|
||||
<CustomListRow
|
||||
id={id}
|
||||
theme={theme}
|
||||
onPress={this.onPress}
|
||||
enabled={enabled}
|
||||
selectable={selectable}
|
||||
selected={selected}
|
||||
id={this.props.id}
|
||||
theme={this.props.theme}
|
||||
onPress={this.props.onPress ? this.onPress : null}
|
||||
enabled={this.props.enabled}
|
||||
selectable={this.props.selectable}
|
||||
selected={this.props.selected}
|
||||
>
|
||||
<ProfilePicture
|
||||
userId={id}
|
||||
userId={this.props.user.id}
|
||||
size={32}
|
||||
/>
|
||||
<View style={style.textContainer}>
|
||||
<View>
|
||||
<Text style={style.displayName}>
|
||||
{displayUsername(user, teammateNameDisplay)}
|
||||
{displayUsername(this.props.user, this.props.teammateNameDisplay)}
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
@@ -83,7 +55,7 @@ export default class UserListRow extends React.PureComponent {
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{usernameDisplay}
|
||||
{`(@${this.props.user.username})`}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -99,24 +71,24 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
height: 65,
|
||||
paddingHorizontal: 15,
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
backgroundColor: theme.centerChannelBg
|
||||
},
|
||||
displayName: {
|
||||
fontSize: 15,
|
||||
color: theme.centerChannelColor,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
icon: {
|
||||
fontSize: 20,
|
||||
color: theme.centerChannelColor,
|
||||
color: theme.centerChannelColor
|
||||
},
|
||||
textContainer: {
|
||||
flexDirection: 'row',
|
||||
marginLeft: 5,
|
||||
marginLeft: 5
|
||||
},
|
||||
username: {
|
||||
marginLeft: 5,
|
||||
fontSize: 15,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5)
|
||||
},
|
||||
selector: {
|
||||
height: 28,
|
||||
@@ -125,20 +97,20 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
borderWidth: 1,
|
||||
borderColor: '#888',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
selectorContainer: {
|
||||
height: 50,
|
||||
paddingRight: 15,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
selectorDisabled: {
|
||||
backgroundColor: '#888',
|
||||
backgroundColor: '#888'
|
||||
},
|
||||
selectorFilled: {
|
||||
backgroundColor: '#378FD2',
|
||||
borderWidth: 0,
|
||||
},
|
||||
borderWidth: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user