Compare commits

..

8 Commits

Author SHA1 Message Date
Elias Nahum
78aba1fcb6 Bump Android build number to 122 (#1883) 2018-07-04 10:50:39 -04:00
Elias Nahum
cba4908854 Bump iOS build number to 122 (#1882) 2018-07-04 10:41:47 -04:00
Harrison Healey
9169f6f694 Add setNativeProps to QuickTextInput (#1880) 2018-07-04 09:38:00 -04:00
Elias Nahum
bdc487ab20 Bump Android build number to 121 (#1875) 2018-07-03 19:44:54 -04:00
Elias Nahum
d7cc95c7f8 Bump iOS build number to 121 (#1874) 2018-07-03 19:39:18 -04:00
Elias Nahum
52ad889bfc Bump Android build number to 120 (#1869) 2018-07-03 19:20:07 -04:00
Elias Nahum
7be6911b2c Bump iOS build number to 120 (#1868) 2018-07-03 19:19:02 -04:00
Harrison Healey
509dfe5542 MM-11116 Re-added QuickTextInput and added another hack on top of it (#1871)
* MM-11116 Re-added updated QuickTextInput component to work around RN issue

* MM-11116 Work around setNativeProps not working for TextInputs

* Add isFocused method to QuickTextInput
2018-07-03 19:04:04 -04:00
616 changed files with 7561 additions and 25477 deletions

View File

@@ -11,8 +11,7 @@
},
"parser": "babel-eslint",
"plugins": [
"react",
"header"
"react"
],
"env": {
"browser": true,
@@ -32,8 +31,7 @@
"expect": true,
"it": true,
"jest": true,
"test": true,
"__DEV__": true
"test": true
},
"rules": {
"array-bracket-spacing": [2, "never"],
@@ -48,7 +46,7 @@
"comma-dangle": [2, "always-multiline"],
"comma-spacing": [2, {"before": false, "after": true}],
"comma-style": [2, "last"],
"complexity": [0, 10],
"complexity": [1, 10],
"computed-property-spacing": [2, "never"],
"consistent-return": 2,
"consistent-this": [2, "self"],
@@ -63,7 +61,6 @@
"generator-star-spacing": [0, {"before": false, "after": true}],
"global-require": 0,
"guard-for-in": 2,
"header/header": [2, "line", " Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n See LICENSE.txt for license information."],
"id-blacklist": 0,
"indent": [2, 4, {"SwitchCase": 0}],
"jsx-quotes": [2, "prefer-single"],

1
.gitignore vendored
View File

@@ -20,7 +20,6 @@ build/
*.perspectivev3
!default.perspectivev3
xcuserdata
xcshareddata
*.xccheckout
*.moved-aside
DerivedData

View File

@@ -1,152 +1,5 @@
# Mattermost Mobile Apps Changelog
## v1.11.0 Release
- Release Date: August 16, 2018
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported
### Highlights
#### Searching Archived Channels
- Added ability to search for archived channels. Requires Mattermost server v5.2 or later.
#### Deep Linking
- Added the ability for custom builds to open Mattermost links directly in the app rather than the default mobile browser. Learn more in our [documentation](https://docs.mattermost.com/mobile/mobile-faq.html#how-do-i-configure-deep-linking)
### Improvements
- Added profile pop-up to combined system messages.
- Force re-entering SSO auth credentials after logout.
- Added consecutive posts by the same user.
- Added a loading indicator when user info is still loading in the left-hand side.
### Bug Fixes
- Fixed an issue where Android devices showed an incorrect timestamp.
- Fixed an issue on Android where the app did not get sent to the background when pressing the hardware back button in the channel screen.
- Fixed an issue with video playback when the filename had spaces.
- Fixed an issue where the app crashed when playing YouTube videos.
- Fixed an issue with session expiration notification.
- Fixed an issue with sharing files from Google Drive in Android Share Extension.
- Fixed an issue on Android where replying to a push notification sometimes went to the wrong channel.
- Fixed an issue where the previous server URL was present on the input textbox before changing the screen to Login.
- Fixed an issue where user menu was not translated correctly.
- Fixed an issue where some field lengths in Account Settings didn't match the desktop app.
- Fixed an issue where long URLs for embedded images in message attachments got cut off and didn't render.
- Fixed an issue where link preview images were not cropped properly.
- Fixed an issue where long usernames didn't wrap properly in the Account Settings menu.
- Fixed an issue where DMs would not open if users were using "Jump To".
- Fixed an issue where no message was displayed after removing a user from a channel with join/leave messages disabled.
## v1.10.0 Release
- Release Date: July 16, 2018
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
### Highlights
#### Channel drawer performance
- Android devices will notice significant performance improvements when opening and closing the channel drawer.
#### Channel loading performance
- Improved channel loading performance as post are retrieved with every push notification
#### Announcement banner improvements
- Markdown now renders when announcement banners are expanded
- When enabled by the System Admin, users can now dismiss announcement banners until their next session
### Improvements
- Combined consecutive messages from the same user.
- Added experimental support for certificate-based authentication (CBA) for iOS to identify a user or a device before granting access to Mattermost. See [documentation](https://docs.mattermost.com/deployment/certificate-based-authentication.html) to learn more.
- Added support for the experimental automatic direct message replies feature.
- Added support for the experimental timezone feature.
- Changed post textbox to not be a connected component.
- Allow connecting to mattermost instances hosted at subpaths.
- Added support for starting YouTube videos at a given time.
- Added support for keeping messages if slash command fails.
### Bug Fixes
- Fixed an issue where the unread badge background was always white.
- Fixed an issue where a username repeated in system message if user was added to a channel more than once.
- Fixed an issue where Android Sharing from Microsoft apps failed.
- Fixed an issue where YouTube crashed the app if link did not have a time set.
- Fixed an issue where System Admins did not see all teams available to join on mobile.
- Fixed an issue where users were unable to share from Files app.
- Fixed an issue where viewing a non-existent permalink didn't show an error message.
- Fixed an issue where jumping to a channel search did not bold unread channels.
- Fixed an issue with being able to add own user to a Group Message channel.
- Fixed an issue with not being able to reply from a push notification on iOS.
- Fixed an issue where the app did not display Brazilian language.
## 1.9.3 Release
- Release Date: July 04, 2018
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
### Bug Fixes
- Fixed multiple issues causing app crashes
- Fixed an issue on iOS devices with typing non-english characters in the post input box
## 1.9.2 Release
- Release Date: June 27, 2018
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
### Bug Fixes
- Fixed an issue where attached videos did not play for the poster
- Fixed an issue where "Jump to recent messages" from the permalink view did not direct the user to the bottom of the channel
- Fixed an issue where post comments did not identify which parent post they belonged to
- Fixed multiple issues with typing non-english characters in the post input box
- Fixed multiple issues causing random app crashes
- Fixed an issue where files from the Android Files app failed to upload
- Fixed an issue where the iOS share extension crashed when switching the team or channel
- Fixed an issue where files from the Microsoft app failed to upload
- Fixed an issue on Android devices where sharing files changed the file extension of the attachment
## 1.9.1 Release
- Release Date: June 23, 2018
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
### Bug Fixes
- Fixed an issue with typing lag on Android devices
- Fixed an issue causing users to be logged out after upgrading to v1.9.0
- Fixed an issue where the ``in:`` and ``from:`` modifiers were not being added to the search field
## v1.9.0 Release
- Release Date: June 16, 2018
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported
### Highlights
#### Improved first load time on Android
- Significantly decreased first load time on Android devices from cold start.
#### iOS Files app support
- Added support for attaching files from the iOS Files app from within Mattermost.
#### Improved styling of push notification
- Improved the layout of message content, channel name and sender name in push notifications.
### Improvements
- Combined join/leave system messages.
- Added splash screen and channel loader improvements.
- Removed the desktop notification duration setting.
- Added cache team icon and set background to always be white if using a PNG file.
- Added whitelabel for icons and splash screen.
### Bug Fixes
- Fixed an issue where other user's display name did not render in combined system messages after joining the channel.
- Fixed an issue where posts incorrectly had "Commented on Someone's message" above them.
- Fixed an issue where deleting a post or its parent in permalink view left permalink view blank.
- Fixed an issue where "User is typing" message cut was off.
- Fixed an issue where `More New Messages Above` appeared at the top of new channel on joining.
- Fixed an issue where a user was not directed to Town Square when leaving a channel.
- Fixed an issue where long post were not collapsed on Android.
- Fixed an issue where a user's name was initially shown as "someone" when opening a direct message with the user.
- Fixed an issue where an error was received when trying to change the team or channel from the share extension.
- Fixed an issue where switching to a newly created channel from a push notification redirected a user to Town Square.
- Fixed an issue where a public channel made private did not disappear automatically from clients not part of the channel.
## v1.8.0 Release
- Release Date: April 27, 2018
- Server Versions Supported: Server v4.0+ is required, Self-Signed SSL Certificates are not supported

View File

@@ -1,6 +1,6 @@
# Code Contribution Guidelines
Thank you for your interest in contributing! Please see the [Mattermost Contribution Guide](https://developers.mattermost.com/contribute/getting-started/) which describes the process for making code contributions across Mattermost projects and [join our "Native Mobile Apps" community channel](https://pre-release.mattermost.com/core/channels/native-mobile-apps) to ask questions from community members and the Mattermost core team.
Thank you for your interest in contributing! Please see the [Mattermost Contribution Guide](http://docs.mattermost.com/developer/contribution-guide.html) which describes the process for making code contributions across Mattermost projects and [join our "Native Mobile Apps" community channel](https://pre-release.mattermost.com/core/channels/native-mobile-apps) to ask questions from community members and the Mattermost core team.
### Review Process for this Repo

View File

@@ -87,7 +87,7 @@ post-install:
@cd ./node_modules/mattermost-redux && npm run build
start: | pre-run ## Starts the React Native packager server
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start; \
else \
@@ -139,7 +139,7 @@ prepare-android-build:
run: run-ios ## alias for run-ios
run-ios: | check-device-ios pre-run ## Runs the app on an iOS simulator
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo Running iOS app in development; \
if [ ! -z "${SIMULATOR}" ]; then \
@@ -158,7 +158,7 @@ run-ios: | check-device-ios pre-run ## Runs the app on an iOS simulator
fi
run-android: | check-device-android pre-run prepare-android-build ## Runs the app on an Android emulator or dev device
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo Running Android app in development; \
if [ ! -z ${VARIANT} ]; then \
@@ -177,25 +177,25 @@ run-android: | check-device-android pre-run prepare-android-build ## Runs the ap
fi
build-ios: | pre-run check-style ## Creates an iOS build
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo; \
fi
@echo "Building iOS app"
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane ios build
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
@ps -e | grep -i "cli.js start" | grep -iv grep | awk '{print $$1}' | xargs kill -9
build-android: | pre-run check-style prepare-android-build ## Creates an Android build
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo; \
fi
@echo "Building Android app"
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane android build
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
@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 -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo; \
fi
@@ -206,17 +206,17 @@ unsigned-ios: pre-run check-style
@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/
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
@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 -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
@if [ $(shell ps -e | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo; \
fi
@echo "Building unsigned Android app"
@cd fastlane && NODE_ENV=production bundle exec fastlane android unsigned
@mv android/app/build/outputs/apk/unsigned/app-unsigned-unsigned.apk ./Mattermost-unsigned.apk
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
@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
@npm test

View File

@@ -973,6 +973,25 @@ Contact GitHub API Training Shop Blog About
---
## jsdom-global
jsdom-global will inject document, window and other DOM API into your Node.js environment. Useful for running, in Node.js, tests that are made for browsers.
* HOMEPAGE
* https://github.com/rstacruz/jsdom-global
* LICENSE
MIT License
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.
---
## url-parse
This product contains 'url-parse.js', Small footprint URL parser that works seamlessly across Node.js and browser environments.
@@ -1038,6 +1057,88 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
## NYC
Istanbul's state of the art command line interface
* HOMEPAGE
* https://github.com/istanbuljs/nyc
* LICENSE
ISC License
Copyright (c) 2015, Contributors
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice
appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE
LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---
## Enzyme
JavaScript Testing utilities for React
* HOMEPAGE
* http://airbnb.io/enzyme/
* LICENSE
The MIT License (MIT)
Copyright (c) 2015 Airbnb, Inc.
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.
---
## Chai
BDD / TDD assertion framework for node.js and the browser that can be paired with any testing framework.
* HOMEPAGE
* http://chaijs.com/
* LICENSE
MIT License
Copyright (c) 2016 Chai.js Assertion Library
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.
---
## TinyColor
This product contains 'tinycolor', a small, fast library for color manipulation and conversion in JavaScript. It allows many forms of input, while providing color conversions and other color utility functions
@@ -1072,6 +1173,40 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
## react-native-search-box
This product contains a modified portion of 'react-native-search-box', a simple search box with animation for React Native.
* HOMEPAGE:
* https://github.com/crabstudio
* LICENSE:
The MIT License
Copyright (c) 2017 Crabstudio.
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-exception-handler
This product contains 'react-native-exception-handler', A react native module that lets you to register a global error handler that can capture fatal/non fatal uncaught exceptions.
@@ -1428,6 +1563,40 @@ SOFTWARE.
---
## youtube-video-id
This product contains 'youtube-video-id', Extracts the YouTube video ID from a url or string.
* HOMEPAGE:
* https://github.com/remarkablemark/youtube-video-id
* LICENSE:
MIT License
Copyright (c) 2016 Menglin "Mark" Xu <mark@remarkablemark.org>
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-video
This product contains 'react-native-video', A <Video> component for react-native.
@@ -1983,6 +2152,40 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
---
## socketcluster
SocketCluster is a fast, highly scalable HTTP + realtime server engine which lets you build multi-process realtime servers that make use of all CPU cores on a machine/instance.
* HOMEPAGE:
* https://github.com/SocketCluster/socketcluster
* LICENSE:
(The MIT License)
Copyright (c) 2013-2017 SocketCluster.io
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.
---
## commonmark
This product contains a modified version of 'commonmark'. CommonMark is a rationalized version of Markdown syntax, with a spec and BSD-licensed reference implementations in C and JavaScript.
@@ -2110,6 +2313,219 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE
---
## react-native-media-controls
This product contains a modified version of 'react-native-media-controls' This project is a UI component to manipulate your media.
* HOMEPAGE:
* https://github.com/charliesbox/react-native-media-controls
* 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 [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.
---
## react-native-section-list-get-item-layout
This package provides a function that helps you construct the getItemLayout function for your SectionLists.
@@ -2202,99 +2618,3 @@ 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.
---
## mime-db
This is a database of all mime types.
* HOMEPAGE:
* https://github.com/jshttp/mime-db
* LICENSE:
The MIT License (MIT)
Copyright (c) 2014 Jonathan Ong me@jongleberry.com
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-document-picker
Document Picker for React Native using Document Providers.
* HOMEPAGE:
* https://github.com/Elyx0/react-native-document-picker
* LICENSE:
MIT License
Copyright (c) 2016 Elyx0
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-image-gallery
Pure JavaScript image gallery component for iOS and Android.
* HOMEPAGE:
* https://github.com/archriss/react-native-image-gallery
* LICENSE:
---
## react-native-keychain
Keychain Access for React Native.
* HOMEPAGE:
* https://github.com/oblador/react-native-keychain
* LICENSE:
The MIT License (MIT)
Copyright (c) 2015 Joel Arvidsson
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.

View File

@@ -1,8 +1,9 @@
# Mattermost Mobile
- **Supported Server versions:** 4.0+
- **Supported iOS versions:** 9.3+
- **Supported Android versions:** 5.0+
**Supported Server Versions:** 4.0+
**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 14 languages. Learn more at [https://about.mattermost.com](https://about.mattermost.com).
@@ -10,8 +11,6 @@ You can download our apps from the [App Store](https://about.mattermost.com/matt
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!
**Important:** If you self-compile the Mattermost Mobile apps you also need to self-compile and deploy your own [Mattermost Push Notification Service](https://github.com/mattermost/mattermost-push-proxy).
# How to Contribute
### Testing

View File

@@ -106,16 +106,15 @@ def enableSeparateBuildPerCPUArchitecture = false
def enableProguardInReleaseBuilds = false
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdkVersion 25
buildToolsVersion "25.0.1"
defaultConfig {
applicationId "com.mattermost.rnbeta"
minSdkVersion 21
targetSdkVersion 26
versionCode 142
versionName "1.12.0"
multiDexEnabled = true
targetSdkVersion 23
versionCode 122
versionName "1.9.3"
ndk {
abiFilters "armeabi-v7a", "x86"
}
@@ -152,7 +151,6 @@ android {
unsigned.initWith(buildTypes.release)
unsigned {
signingConfig null
matchingFallbacks = ['debug', 'release']
}
}
// applicationVariants are e.g. debug, release
@@ -187,37 +185,39 @@ configurations.all {
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:27.1.1"
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:percent:27.1.1'
implementation "com.facebook.react:react-native:+" // From node_modules
implementation project(':react-native-document-picker')
implementation project(':react-native-keychain')
implementation project(':react-native-doc-viewer')
implementation project(':react-native-video')
implementation project(':react-native-navigation')
implementation project(':react-native-image-picker')
implementation project(':react-native-bottom-sheet')
implementation project(':react-native-device-info')
implementation project(':reactnativenotifications')
implementation project(':react-native-cookies')
implementation project(':react-native-linear-gradient')
implementation project(':react-native-vector-icons')
implementation project(':react-native-svg')
implementation project(':react-native-local-auth')
implementation project(':jail-monkey')
implementation project(':react-native-youtube')
implementation project(':react-native-sentry')
implementation project(':react-native-exception-handler')
implementation project(':rn-fetch-blob')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:25.0.1"
compile 'com.android.support:percent:25.3.1'
compile "com.facebook.react:react-native:+" // From node_modules
compile project(':react-native-document-picker')
compile project(':react-native-keychain')
compile project(':react-native-doc-viewer')
compile project(':react-native-video')
compile project(':react-native-navigation')
compile project(':react-native-image-picker')
compile project(':react-native-bottom-sheet')
compile ('com.google.android.gms:play-services-gcm:9.4.0') {
force = true;
}
compile project(':react-native-device-info')
compile project(':reactnativenotifications')
compile project(':react-native-cookies')
compile project(':react-native-linear-gradient')
compile project(':react-native-vector-icons')
compile project(':react-native-svg')
compile project(':react-native-local-auth')
compile project(':jail-monkey')
compile project(':react-native-youtube')
compile project(':react-native-sentry')
compile project(':react-native-exception-handler')
compile project(':react-native-fetch-blob')
// For animated GIF support
implementation 'com.facebook.fresco:animated-base-support:1.3.0'
compile 'com.facebook.fresco:animated-base-support:1.3.0'
// For WebP support, including animated WebP
implementation 'com.facebook.fresco:animated-gif:1.3.0'
implementation 'com.facebook.fresco:animated-webp:1.3.0'
implementation 'com.facebook.fresco:webpsupport:1.3.0'
compile 'com.facebook.fresco:animated-gif:1.3.0'
compile 'com.facebook.fresco:animated-webp:1.3.0'
compile 'com.facebook.fresco:webpsupport:1.3.0'
}
// Run this once to be able to run the application with BUCK

View File

@@ -5,11 +5,18 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.google.android.c2dm.permission.SEND" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-sdk

View File

@@ -1,7 +1,6 @@
package com.mattermost.rnbeta;
import android.app.PendingIntent;
import android.app.NotificationChannel;
import android.content.Intent;
import android.content.Context;
import android.content.res.Resources;
@@ -141,19 +140,8 @@ public class CustomPushNotification extends PushNotification {
String packageName = mContext.getPackageName();
NotificationPreferences notificationPreferences = NotificationPreferences.getInstance(mContext);
String CHANNEL_ID = "channel_01";
String CHANNEL_NAME = "Mattermost notifications";
// First, get a builder initialized with defaults from the core class.
final Notification.Builder notification = new Notification.Builder(mContext);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT);
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
notification.setChannelId(CHANNEL_ID);
}
Bundle bundle = mNotificationProps.asBundle();
String version = bundle.getString("version");
@@ -283,13 +271,7 @@ public class CustomPushNotification extends PushNotification {
replyIntent.setAction(KEY_TEXT_REPLY);
replyIntent.putExtra(NOTIFICATION_ID, notificationId);
replyIntent.putExtra("pushNotification", bundle);
PendingIntent replyPendingIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
replyPendingIntent = PendingIntent.getForegroundService(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
} else {
replyPendingIntent = PendingIntent.getService(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
PendingIntent replyPendingIntent = PendingIntent.getService(mContext, 103, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
.setLabel("Reply")

View File

@@ -66,7 +66,7 @@ public class MainApplication extends NavigationApplication implements INotificat
new JailMonkeyPackage(),
new RNFetchBlobPackage(),
new MattermostPackage(this),
new RNSentryPackage(),
new RNSentryPackage(this),
new ReactNativeExceptionHandlerPackage(),
new ReactNativeYouTube(),
new ReactVideoPackage(),

View File

@@ -1,13 +1,9 @@
package com.mattermost.rnbeta;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.RemoteInput;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
import android.app.NotificationManager;
import android.app.RemoteInput;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
@@ -22,28 +18,6 @@ import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
public class NotificationReplyService extends HeadlessJsTaskService {
private Context mContext;
@Override
public void onCreate() {
super.onCreate();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
Context mContext = this.getApplicationContext();
final Resources res = mContext.getResources();
String packageName = mContext.getPackageName();
int smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
String CHANNEL_ID = "Reply job";
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
Notification notification =
new Notification.Builder(mContext, CHANNEL_ID)
.setContentTitle("Replying to message")
.setContentText(packageName)
.setSmallIcon(smallIconResId)
.build();
startForeground(1, notification);
}
}
@Override
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
mContext = getApplicationContext();

View File

@@ -6,7 +6,6 @@ import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.content.ContentUris;
import android.content.ContentResolver;
import android.os.Environment;
@@ -96,33 +95,15 @@ public class RealPathUtil {
public static String getPathFromSavingTempFile(Context context, final Uri uri) {
File tmpFile;
String fileName = null;
// Try and get the filename from the Uri
try {
Cursor returnCursor =
context.getContentResolver().query(uri, null, null, null, null);
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
returnCursor.moveToFirst();
fileName = returnCursor.getString(nameIndex);
} catch (Exception e) {
// just continue to get the filename with the last segment of the path
}
try {
if (fileName == null) {
fileName = uri.getLastPathSegment().toString().trim();
}
String fileName = uri.getLastPathSegment();
File cacheDir = new File(context.getCacheDir(), "mmShare");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
String mimeType = getMimeType(uri.getPath());
tmpFile = new File(cacheDir, fileName);
tmpFile.createNewFile();
tmpFile = File.createTempFile("tmp", fileName, cacheDir);
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");

BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 459 KiB

BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 585 KiB

BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 590 KiB

BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 468 KiB

BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 608 KiB

View File

@@ -2,12 +2,11 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'com.google.gms:google-services:3.2.0'
classpath 'com.android.tools.build:gradle:2.2.+'
classpath 'com.google.gms:google-services:3.1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -16,7 +15,6 @@ buildscript {
allprojects {
repositories {
google()
mavenLocal()
jcenter()
maven {
@@ -29,18 +27,3 @@ allprojects {
}
}
}
ext {
compileSdkVersion = 26
buildToolsVersion = '26.0.2'
}
subprojects { subproject ->
afterEvaluate{
if((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
}
}
}
}

View File

@@ -11,12 +11,10 @@
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx2048M
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.enableAapt2=false
android.useDeprecatedNdk=true

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

View File

@@ -13,8 +13,8 @@ include ':react-native-sentry'
project(':react-native-sentry').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sentry/android')
include ':react-native-exception-handler'
project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-exception-handler/android')
include ':rn-fetch-blob'
project(':rn-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/rn-fetch-blob/android')
include ':react-native-fetch-blob'
project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android')
include ':jail-monkey'
project(':jail-monkey').projectDir = new File(rootProject.projectDir, '../node_modules/jail-monkey/android')
include ':react-native-local-auth'

View File

@@ -1,19 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {networkStatusChangedAction} from 'redux-offline';
import {Client4} from 'mattermost-redux/client';
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {DeviceTypes} from 'app/constants';
export function connection(isOnline) {
return async (dispatch) => {
Client4.setOnline(isOnline);
dispatch(networkStatusChangedAction(isOnline));
return async (dispatch, getState) => {
dispatch({
type: DeviceTypes.CONNECTION_CHANGED,
data: isOnline,
});
}, getState);
};
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {updateMe} from 'mattermost-redux/actions/users';
import {Preferences} from 'mattermost-redux/constants';
import {savePreferences} from 'mattermost-redux/actions/preferences';
export function handleUpdateUserNotifyProps(notifyProps) {
return async (dispatch, getState) => {
const state = getState();
const config = state.entities.general.config;
const {currentUserId} = state.entities.users;
const {interval, user_id: userId, ...otherProps} = notifyProps;
const email = notifyProps.email;
if (config.EnableEmailBatching === 'true' && email !== 'false') {
const emailInterval = [{
user_id: userId,
category: Preferences.CATEGORY_NOTIFICATIONS,
name: Preferences.EMAIL_INTERVAL,
value: interval,
}];
savePreferences(currentUserId, emailInterval)(dispatch, getState);
}
const props = {...otherProps, email};
try {
await updateMe({notify_props: props})(dispatch, getState);
} catch (error) {
// do nothing
}
};
}

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {ViewTypes} from 'app/constants';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {batchActions} from 'redux-batched-actions';
@@ -208,7 +208,7 @@ export function loadPostsIfNecessaryWithRetry(channelId) {
export async function retryGetPostsAction(action, dispatch, getState, maxTries = MAX_POST_TRIES) {
for (let i = 0; i < maxTries; i++) {
const {data} = await dispatch(action);
const {data} = await action(dispatch, getState);
if (data) {
dispatch(setChannelRetryFailed(false));
@@ -259,11 +259,8 @@ export function selectInitialChannel(teamId) {
const isGMVisible = lastChannel && lastChannel.type === General.GM_CHANNEL &&
isGroupChannelVisible(myPreferences, lastChannel);
if (
myMembers[lastChannelId] &&
lastChannel &&
(lastChannel.team_id === teamId || isDMVisible || isGMVisible)
) {
if (lastChannelId && myMembers[lastChannelId] &&
(lastChannel.team_id === teamId || isDMVisible || isGMVisible)) {
handleSelectChannel(lastChannelId)(dispatch, getState);
markChannelAsRead(lastChannelId)(dispatch, getState);
return;
@@ -514,19 +511,17 @@ export function increasePostVisibility(channelId, focusedPostId) {
}];
const posts = result.data;
let hasMorePost = false;
if (posts) {
hasMorePost = posts.order.length >= pageSize;
// make sure to increment the posts visibility
// only if we got results
actions.push(doIncreasePostVisibility(channelId));
actions.push(setLoadMorePostsVisible(hasMorePost));
actions.push(setLoadMorePostsVisible(posts.order.length >= pageSize));
}
dispatch(batchActions(actions));
return hasMorePost;
return Boolean(posts);
};
}

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {addChannelMember} from 'mattermost-redux/actions/channels';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {removeChannelMember} from 'mattermost-redux/actions/channels';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {ViewTypes} from 'app/constants';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {executeCommand as executeCommandService} from 'mattermost-redux/actions/integrations';
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {handleSelectChannel, setChannelDisplayName} from './channel';
import {createChannel} from 'mattermost-redux/actions/channels';

View File

@@ -1,9 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {updateMe} from 'mattermost-redux/actions/users';
import {ViewTypes} from 'app/constants';
import {uploadProfileImage, updateMe} from 'mattermost-redux/actions/users';
import {buildFileUploadData} from 'app/utils/file';
export function updateUser(user, success, error) {
return async (dispatch, getState) => {
@@ -18,14 +17,9 @@ export function updateUser(user, success, error) {
};
}
export function setProfileImageUri(imageUri = '') {
return {
type: ViewTypes.SET_PROFILE_IMAGE_URI,
imageUri,
export function handleUploadProfileImage(image, userId) {
return async (dispatch, getState) => {
const imageData = buildFileUploadData(image);
return await uploadProfileImage(userId, imageData)(dispatch, getState);
};
}
export default {
updateUser,
setProfileImageUri,
};

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {addReaction as serviceAddReaction} from 'mattermost-redux/actions/posts';
import {getPostIdsInCurrentChannel, makeGetPostIdsForThread} from 'mattermost-redux/selectors/entities/posts';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {FileTypes} from 'mattermost-redux/action_types';

View File

@@ -1,17 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// 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 {getSessions} from 'mattermost-redux/actions/users';
import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone';
import {Client4} from 'mattermost-redux/client';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {ViewTypes} from 'app/constants';
import {app} from 'app/mattermost';
import {getDeviceTimezone, isTimezoneEnabled} from 'app/utils/timezone';
export function handleLoginIdChanged(loginId) {
return async (dispatch, getState) => {
@@ -34,8 +30,7 @@ export function handlePasswordChanged(password) {
export function handleSuccessfulLogin() {
return async (dispatch, getState) => {
const state = getState();
const config = getConfig(state);
const license = getLicense(state);
const {config, license} = state.entities.general;
const token = Client4.getToken();
const url = Client4.getUrl();
const deviceToken = state.entities.general.deviceToken;
@@ -43,11 +38,6 @@ export function handleSuccessfulLogin() {
app.setAppCredentials(deviceToken, currentUserId, token, url);
const enableTimezone = isTimezoneEnabled(state);
if (enableTimezone) {
dispatch(autoUpdateTimezone(getDeviceTimezone()));
}
dispatch({
type: GeneralTypes.RECEIVED_APP_CREDENTIALS,
data: {
@@ -71,27 +61,18 @@ export function getSession() {
return async (dispatch, getState) => {
const state = getState();
const {currentUserId} = state.entities.users;
const {deviceToken} = state.entities.general;
const {credentials} = state.entities.general;
const token = credentials && credentials.token;
if (!currentUserId || !deviceToken) {
return 0;
if (currentUserId && token) {
const session = await Client4.getSessions(currentUserId, token);
if (Array.isArray(session) && session[0]) {
const s = session[0];
return s.expires_at;
}
}
let sessions;
try {
sessions = await dispatch(getSessions(currentUserId));
} catch (e) {
console.warn('Failed to get current session', e); // eslint-disable-line no-console
return 0;
}
if (!Array.isArray(sessions.data)) {
return 0;
}
const session = sessions.data.find((s) => s.device_id === deviceToken);
return session && session.expires_at ? session.expires_at : 0;
return false;
};
}

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See License.txt for license information.
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
@@ -11,7 +11,7 @@ import {
handlePasswordChanged,
} from 'app/actions/views/login';
jest.mock('rn-fetch-blob/fs', () => ({
jest.mock('react-native-fetch-blob/fs', () => ({
dirs: {
DocumentDir: () => jest.fn(),
CacheDir: () => jest.fn(),

View File

@@ -1,12 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {getDirectChannelName} from 'mattermost-redux/utils/channel_utils';
import {createDirectChannel, createGroupChannel} from 'mattermost-redux/actions/channels';
import {getProfilesByIds, getStatusesByIds} from 'mattermost-redux/actions/users';
import {handleSelectChannel, toggleDMChannel, toggleGMChannel} from 'app/actions/views/channel';
export function makeDirectChannel(otherUserId, switchToChannel = true) {
export function makeDirectChannel(otherUserId) {
return async (dispatch, getState) => {
const state = getState();
const {currentUserId} = state.entities.users;
@@ -23,11 +23,11 @@ export function makeDirectChannel(otherUserId, switchToChannel = true) {
dispatch(toggleDMChannel(otherUserId, 'true', channel.id));
} else {
result = await dispatch(createDirectChannel(currentUserId, otherUserId));
result = await createDirectChannel(currentUserId, otherUserId)(dispatch, getState);
channel = result.data;
}
if (channel && switchToChannel) {
if (channel) {
dispatch(handleSelectChannel(channel.id));
}

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See License.txt for license information.
import {Posts} from 'mattermost-redux/constants';
import {PostTypes} from 'mattermost-redux/action_types';

View File

@@ -1,11 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {GeneralTypes, PostTypes} from 'mattermost-redux/action_types';
import {Client4} from 'mattermost-redux/client';
import {General} from 'mattermost-redux/constants';
import {fetchMyChannelsAndMembers, markChannelAsRead} from 'mattermost-redux/actions/channels';
import {getClientConfig, getDataRetentionPolicy, getLicenseConfig} from 'mattermost-redux/actions/general';
import {getPosts} from 'mattermost-redux/actions/posts';
import {getMyTeams, getMyTeamMembers, selectTeam} from 'mattermost-redux/actions/teams';
import {ViewTypes} from 'app/constants';
@@ -14,6 +15,7 @@ import {recordTime} from 'app/utils/segment';
import {
handleSelectChannel,
setChannelDisplayName,
retryGetPostsAction,
} from 'app/actions/views/channel';
export function startDataCleanup() {
@@ -55,15 +57,10 @@ export function loadFromPushNotification(notification) {
const {data} = notification;
const {currentTeamId, teams, myMembers: myTeamMembers} = state.entities.teams;
const {currentChannelId, channels} = state.entities.channels;
const channelId = data.channel_id;
let channelId = '';
let teamId = currentTeamId;
if (data) {
channelId = data.channel_id;
// when the notification does not have a team id is because its from a DM or GM
teamId = data.team_id || currentTeamId;
}
// when the notification does not have a team id is because its from a DM or GM
const teamId = data.team_id || currentTeamId;
// load any missing data
const loading = [];
@@ -86,10 +83,12 @@ export function loadFromPushNotification(notification) {
dispatch(selectTeam({id: teamId}));
}
// mark channel as read
dispatch(markChannelAsRead(channelId, channelId === currentChannelId ? null : currentChannelId, false));
if (channelId !== currentChannelId) {
// when the notification is from the same channel as the current channel
// we should get the posts
if (channelId === currentChannelId) {
dispatch(markChannelAsRead(channelId, null, false));
await retryGetPostsAction(getPosts(channelId), dispatch, getState);
} else {
// when the notification is from a channel other than the current channel
dispatch(markChannelAsRead(channelId, currentChannelId, false));
dispatch(setChannelDisplayName(''));
@@ -102,9 +101,7 @@ export function purgeOfflineStore() {
return {type: General.OFFLINE_STORE_PURGE};
}
// A non-optimistic version of the createPost action in mattermost-redux with the file handling
// removed since it's not needed.
export function createPostForNotificationReply(post) {
export function createPost(post) {
return (dispatch, getState) => {
const state = getState();
const currentUserId = state.entities.users.currentUserId;
@@ -142,13 +139,6 @@ export function recordLoadTime(screenName, category) {
};
}
export function setDeepLinkURL(url) {
return {
type: ViewTypes.SET_DEEP_LINK_URL,
url,
};
}
export default {
loadConfigAndLicense,
loadFromPushNotification,

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {ViewTypes} from 'app/constants';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// 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';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See License.txt for license information.
import {batchActions} from 'redux-batched-actions';
import configureStore from 'redux-mock-store';
@@ -11,7 +11,7 @@ import {ViewTypes} from 'app/constants';
import {handleServerUrlChanged} from 'app/actions/views/select_server';
jest.mock('rn-fetch-blob/fs', () => ({
jest.mock('react-native-fetch-blob/fs', () => ({
dirs: {
DocumentDir: () => jest.fn(),
CacheDir: () => jest.fn(),

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {batchActions} from 'redux-batched-actions';
@@ -7,12 +7,10 @@ import {markChannelAsRead, markChannelAsViewed} from 'mattermost-redux/actions/c
import {ChannelTypes, TeamTypes} from 'mattermost-redux/action_types';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels';
import {RequestStatus} from 'mattermost-redux/constants';
import {NavigationTypes} from 'app/constants';
import {setChannelDisplayName} from './channel';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
export function handleTeamChange(teamId, selectChannel = true) {
return async (dispatch, getState) => {
@@ -42,7 +40,7 @@ export function selectDefaultTeam() {
return async (dispatch, getState) => {
const state = getState();
const {ExperimentalPrimaryTeam} = getConfig(state);
const {ExperimentalPrimaryTeam} = state.entities.general.config;
const {teams: allTeams, myMembers} = state.entities.teams;
const teams = Object.keys(myMembers).map((key) => allTeams[key]);
@@ -57,8 +55,6 @@ export function selectDefaultTeam() {
if (defaultTeam) {
handleTeamChange(defaultTeam.id)(dispatch, getState);
} else if (state.requests.teams.getTeams.status === RequestStatus.FAILURE || state.requests.teams.getMyTeams.status === RequestStatus.FAILURE) {
EventEmitter.emit(NavigationTypes.NAVIGATION_ERROR_TEAMS);
} else {
EventEmitter.emit(NavigationTypes.NAVIGATION_NO_TEAMS);
}

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {ViewTypes} from 'app/constants';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See License.txt for license information.
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
@@ -11,7 +11,7 @@ import {
handleCommentDraftSelectionChanged,
} from 'app/actions/views/thread';
jest.mock('rn-fetch-blob/fs', () => ({
jest.mock('react-native-fetch-blob/fs', () => ({
dirs: {
DocumentDir: () => jest.fn(),
CacheDir: () => jest.fn(),

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {userTyping as wsUserTyping} from 'mattermost-redux/actions/websocket';

View File

@@ -1,15 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See License.txt for license information.
/* eslint-disable global-require*/
import {AsyncStorage, Linking, NativeModules, Platform} from 'react-native';
import {AsyncStorage, NativeModules} from 'react-native';
import {setGenericPassword, getGenericPassword, resetGenericPassword} from 'react-native-keychain';
import {loadMe} from 'mattermost-redux/actions/users';
import {Client4} from 'mattermost-redux/client';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {setDeepLinkURL} from 'app/actions/views/root';
import {ViewTypes} from 'app/constants';
import tracker from 'app/utils/time_tracker';
import {getCurrentLocale} from 'app/selectors/i18n';
@@ -51,17 +50,6 @@ export default class App {
this.token = null;
this.url = null;
// Load polyfill for iOS 9
if (Platform.OS === 'ios') {
const majorVersionIOS = parseInt(Platform.Version, 10);
if (majorVersionIOS < 10) {
require('babel-polyfill');
}
}
// Usage deeplinking
Linking.addEventListener('url', this.handleDeepLink);
this.getStartupThemes();
this.getAppCredentials();
}
@@ -103,17 +91,12 @@ export default class App {
const [deviceToken, currentUserId] = usernameParsed;
const [token, url] = passwordParsed;
// if for any case the url and the token aren't valid proceed with re-hydration
if (url && url !== 'undefined' && token && token !== 'undefined') {
this.deviceToken = deviceToken;
this.currentUserId = currentUserId;
this.token = token;
this.url = url;
Client4.setUrl(url);
Client4.setToken(token);
} else {
this.waitForRehydration = true;
}
this.deviceToken = deviceToken;
this.currentUserId = currentUserId;
this.token = token;
this.url = url;
Client4.setUrl(url);
Client4.setToken(token);
}
}
} catch (error) {
@@ -169,7 +152,6 @@ export default class App {
if (!currentUserId) {
return;
}
const username = `${deviceToken}, ${currentUserId}`;
const password = `${token},${url}`;
@@ -179,14 +161,7 @@ export default class App {
this.url = url;
}
// Only save to keychain if the url and token are set
if (url && token) {
try {
setGenericPassword(username, password);
} catch (e) {
console.warn('could not set credentials', e); //eslint-disable-line no-console
}
}
setGenericPassword(username, password);
};
setStartupThemes = (toolbarBackground, toolbarTextColor, appBackground) => {
@@ -240,11 +215,6 @@ export default class App {
]);
};
handleDeepLink = (event) => {
const {url} = event;
store.dispatch(setDeepLinkURL(url));
}
launchApp = async () => {
const shouldStart = await handleManagedConfig();
if (shouldStart) {
@@ -259,21 +229,11 @@ export default class App {
const {dispatch} = store;
Linking.getInitialURL().then((url) => {
dispatch(setDeepLinkURL(url));
});
let screen = 'SelectServer';
if (this.token && this.url) {
screen = 'Channel';
tracker.initialLoad = Date.now();
try {
dispatch(loadMe());
} catch (e) {
// Fall through since we should have a previous version of the current user because we have a token
console.warn('Failed to load current user when starting on Channel screen', e); // eslint-disable-line no-console
}
dispatch(loadMe());
}
switch (screen) {

View File

@@ -5,13 +5,17 @@ ShallowWrapper {
"length": 1,
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <AnnouncementBanner
actions={
Object {
"dismissBanner": [MockFunction],
}
}
allowDismissal={true}
bannerColor="#ddd"
bannerDismissed={false}
bannerEnabled={true}
bannerText="Banner Text"
bannerTextColor="#fff"
navigator={Object {}}
theme={Object {}}
/>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],
@@ -54,9 +58,7 @@ ShallowWrapper {
]
}
>
<RemoveMarkdown
value="Banner Text"
/>
Banner Text
</Text>
<Icon
allowFontScaling={false}
@@ -105,9 +107,7 @@ ShallowWrapper {
]
}
>
<RemoveMarkdown
value="Banner Text"
/>
Banner Text
</Text>,
<Icon
allowFontScaling={false}
@@ -132,9 +132,7 @@ ShallowWrapper {
"props": Object {
"accessible": true,
"allowFontScaling": true,
"children": <RemoveMarkdown
value="Banner Text"
/>,
"children": "Banner Text",
"ellipsizeMode": "tail",
"numberOfLines": 1,
"style": Array [
@@ -149,17 +147,7 @@ ShallowWrapper {
],
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"value": "Banner Text",
},
"ref": null,
"rendered": null,
"type": [Function],
},
"rendered": "Banner Text",
"type": [Function],
},
Object {
@@ -216,9 +204,7 @@ ShallowWrapper {
]
}
>
<RemoveMarkdown
value="Banner Text"
/>
Banner Text
</Text>
<Icon
allowFontScaling={false}
@@ -267,9 +253,7 @@ ShallowWrapper {
]
}
>
<RemoveMarkdown
value="Banner Text"
/>
Banner Text
</Text>,
<Icon
allowFontScaling={false}
@@ -294,9 +278,7 @@ ShallowWrapper {
"props": Object {
"accessible": true,
"allowFontScaling": true,
"children": <RemoveMarkdown
value="Banner Text"
/>,
"children": "Banner Text",
"ellipsizeMode": "tail",
"numberOfLines": 1,
"style": Array [
@@ -311,17 +293,7 @@ ShallowWrapper {
],
},
"ref": null,
"rendered": Object {
"instance": null,
"key": undefined,
"nodeType": "class",
"props": Object {
"value": "Banner Text",
},
"ref": null,
"rendered": null,
"type": [Function],
},
"rendered": "Banner Text",
"type": [Function],
},
Object {
@@ -359,13 +331,17 @@ ShallowWrapper {
"length": 1,
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <AnnouncementBanner
actions={
Object {
"dismissBanner": [MockFunction],
}
}
allowDismissal={true}
bannerColor="#ddd"
bannerDismissed={false}
bannerEnabled={false}
bannerText="Banner Text"
bannerTextColor="#fff"
navigator={Object {}}
theme={Object {}}
/>,
Symbol(enzyme.__renderer__): Object {
"batchedUpdates": [Function],

View File

@@ -1,9 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// 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,
@@ -12,19 +13,19 @@ import {
import {intlShape} from 'react-intl';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import RemoveMarkdown from 'app/components/remove_markdown';
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,
navigator: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
};
static contextTypes = {
@@ -51,24 +52,30 @@ export default class AnnouncementBanner extends PureComponent {
}
}
handlePress = () => {
const {navigator, theme} = this.props;
handleDismiss = () => {
const {actions, bannerText} = this.props;
actions.dismissBanner(bannerText);
};
navigator.push({
screen: 'ExpandedAnnouncementBanner',
title: this.context.intl.formatMessage({
id: 'mobile.announcement_banner.title',
defaultMessage: 'Announcement',
}),
animated: true,
backButtonTitle: '',
navigatorStyle: {
navBarTextColor: theme.sidebarHeaderTextColor,
navBarBackgroundColor: theme.sidebarHeaderBg,
navBarButtonColor: theme.sidebarHeaderTextColor,
screenBackgroundColor: theme.centerChannelBg,
},
});
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) => {
@@ -113,7 +120,7 @@ export default class AnnouncementBanner extends PureComponent {
numberOfLines={1}
style={[style.bannerText, bannerTextStyle]}
>
<RemoveMarkdown value={bannerText}/>
{bannerText}
</Text>
<MaterialIcons
color={bannerTextColor}

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {configure, shallow} from 'enzyme';
@@ -12,13 +12,15 @@ jest.useFakeTimers();
describe('AnnouncementBanner', () => {
const baseProps = {
actions: {
dismissBanner: jest.fn(),
},
allowDismissal: true,
bannerColor: '#ddd',
bannerDismissed: false,
bannerEnabled: true,
bannerText: 'Banner Text',
bannerTextColor: '#fff',
navigator: {},
theme: {},
};
test('should match snapshot', () => {
@@ -31,4 +33,16 @@ describe('AnnouncementBanner', () => {
wrapper.setProps({bannerEnabled: false});
expect(wrapper).toMatchSnapshot();
});
});
test('should call actions.dismissBanner on handleDismiss', () => {
const actions = {dismissBanner: jest.fn()};
const props = {...baseProps, actions};
const wrapper = shallow(
<AnnouncementBanner {...props}/>
);
wrapper.instance().handleDismiss();
expect(actions.dismissBanner).toHaveBeenCalledTimes(1);
expect(actions.dismissBanner).toHaveBeenCalledWith(props.bannerText);
});
});

View File

@@ -1,10 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// 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 {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {dismissBanner} from 'app/actions/views/announcement';
import AnnouncementBanner from './announcement_banner';
@@ -14,13 +16,21 @@ function mapStateToProps(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 || '#000',
theme: getTheme(state),
};
}
export default connect(mapStateToProps)(AnnouncementBanner);
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
dismissBanner,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AnnouncementBanner);

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
@@ -8,8 +8,6 @@ import {intlShape} from 'react-intl';
import {displayUsername} from 'mattermost-redux/utils/user_utils';
import {emptyFunction} from 'app/utils/general';
import CustomPropTypes from 'app/constants/custom_prop_types';
import mattermostManaged from 'app/mattermost_managed';
@@ -27,10 +25,6 @@ export default class AtMention extends React.PureComponent {
usersByUsername: PropTypes.object.isRequired,
};
static defaultProps = {
onLongPress: emptyFunction,
};
static contextTypes = {
intl: intlShape,
};
@@ -80,7 +74,7 @@ export default class AtMention extends React.PureComponent {
};
getUserDetailsFromMentionName(props) {
let mentionName = props.mentionName.toLowerCase();
let mentionName = props.mentionName;
while (mentionName.length > 0) {
if (props.usersByUsername.hasOwnProperty(mentionName)) {

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';

View File

@@ -1,5 +1,3 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {intlShape} from 'react-intl';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
@@ -16,7 +16,6 @@ import AtMention from './at_mention';
import ChannelMention from './channel_mention';
import EmojiSuggestion from './emoji_suggestion';
import SlashSuggestion from './slash_suggestion';
import DateSuggestion from './date_suggestion';
export default class Autocomplete extends PureComponent {
static propTypes = {
@@ -27,13 +26,11 @@ export default class Autocomplete extends PureComponent {
isSearch: PropTypes.bool,
theme: PropTypes.object.isRequired,
value: PropTypes.string,
enableDateSuggestion: PropTypes.bool.isRequired,
};
static defaultProps = {
isSearch: false,
cursorPosition: 0,
enableDateSuggestion: false,
};
state = {
@@ -41,7 +38,6 @@ export default class Autocomplete extends PureComponent {
channelMentionCount: 0,
emojiCount: 0,
commandCount: 0,
dateCount: 0,
keyboardOffset: 0,
};
@@ -61,10 +57,6 @@ export default class Autocomplete extends PureComponent {
this.setState({commandCount});
};
handleIsDateFilterChange = (dateCount) => {
this.setState({dateCount});
};
componentWillMount() {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide);
@@ -93,6 +85,10 @@ export default class Autocomplete extends PureComponent {
}
render() {
if (!this.props.value) {
return null;
}
const style = getStyleFromTheme(this.props.theme);
const wrapperStyle = [];
@@ -105,8 +101,8 @@ export default class Autocomplete extends PureComponent {
}
// We always need to render something, but we only draw the borders when we have results to show
const {atMentionCount, channelMentionCount, emojiCount, commandCount, dateCount} = this.state;
if (atMentionCount + channelMentionCount + emojiCount + commandCount + dateCount > 0) {
const {atMentionCount, channelMentionCount, emojiCount, commandCount} = this.state;
if (atMentionCount + channelMentionCount + emojiCount + commandCount > 0) {
if (this.props.isSearch) {
wrapperStyle.push(style.bordersSearch);
} else {
@@ -114,7 +110,6 @@ export default class Autocomplete extends PureComponent {
}
}
const listHeight = this.listHeight();
return (
<View style={wrapperStyle}>
<View style={containerStyle}>
@@ -136,12 +131,6 @@ export default class Autocomplete extends PureComponent {
onResultCountChange={this.handleCommandCountChange}
{...this.props}
/>
{(this.props.isSearch && this.props.enableDateSuggestion) &&
<DateSuggestion
onResultCountChange={this.handleIsDateFilterChange}
{...this.props}
/>
}
</View>
</View>
);

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
@@ -24,13 +24,11 @@ export default class ChannelMention extends PureComponent {
listHeight: PropTypes.number,
matchTerm: PropTypes.string,
myChannels: PropTypes.array,
myMembers: PropTypes.object,
otherChannels: PropTypes.array,
onChangeText: PropTypes.func.isRequired,
onResultCountChange: PropTypes.func.isRequired,
privateChannels: PropTypes.array,
publicChannels: PropTypes.array,
deletedPublicChannels: PropTypes.instanceOf(Set),
requestStatus: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
value: PropTypes.string,
@@ -50,7 +48,7 @@ export default class ChannelMention extends PureComponent {
}
componentWillReceiveProps(nextProps) {
const {isSearch, matchTerm, myChannels, otherChannels, privateChannels, publicChannels, requestStatus, myMembers, deletedPublicChannels} = nextProps;
const {isSearch, matchTerm, myChannels, otherChannels, privateChannels, publicChannels, requestStatus} = nextProps;
if ((matchTerm !== this.props.matchTerm && matchTerm === null) || this.state.mentionComplete) {
// if the term changes but is null or the mention has been completed we render this component as null
@@ -76,8 +74,7 @@ export default class ChannelMention extends PureComponent {
if (requestStatus !== RequestStatus.STARTED &&
(myChannels !== this.props.myChannels || otherChannels !== this.props.otherChannels ||
privateChannels !== this.props.privateChannels || publicChannels !== this.props.publicChannels ||
myMembers !== this.props.myMembers || deletedPublicChannels !== this.props.deletedPublicChannels)) {
privateChannels !== this.props.privateChannels || publicChannels !== this.props.publicChannels)) {
// if the request is complete and the term is not null we show the autocomplete
const sections = [];
if (isSearch) {
@@ -85,7 +82,7 @@ export default class ChannelMention extends PureComponent {
sections.push({
id: 'suggestion.search.public',
defaultMessage: 'Public Channels',
data: publicChannels.filter((cId) => !deletedPublicChannels.has(cId) || myMembers[cId]),
data: publicChannels,
key: 'publicChannels',
});
}

View File

@@ -1,11 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {searchChannels} from 'mattermost-redux/actions/channels';
import {getMyChannelMemberships} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {
@@ -13,7 +12,6 @@ import {
filterOtherChannels,
filterPublicChannels,
filterPrivateChannels,
getDeletedPublicChannelsIds,
getMatchTermForChannelMention,
} from 'app/selectors/autocomplete';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
@@ -40,10 +38,8 @@ function mapStateToProps(state, ownProps) {
return {
myChannels,
myMembers: getMyChannelMemberships(state),
otherChannels,
publicChannels,
deletedPublicChannels: getDeletedPublicChannelsIds(state),
privateChannels,
currentTeamId: getCurrentTeamId(state),
matchTerm,

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';

View File

@@ -1,152 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {PureComponent} from 'react';
import {Dimensions, Platform, StyleSheet} from 'react-native';
import PropTypes from 'prop-types';
import {CalendarList} from 'react-native-calendars';
import {memoizeResult} from 'mattermost-redux/utils/helpers';
import {DATE_MENTION_SEARCH_REGEX, ALL_SEARCH_FLAGS_REGEX} from 'app/constants/autocomplete';
import {changeOpacity} from 'app/utils/theme';
export default class DateSuggestion extends PureComponent {
static propTypes = {
cursorPosition: PropTypes.number.isRequired,
listHeight: PropTypes.number,
matchTerm: PropTypes.string,
onChangeText: PropTypes.func.isRequired,
onResultCountChange: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
value: PropTypes.string,
enableDateSuggestion: PropTypes.bool.isRequired,
};
static defaultProps = {
value: '',
};
constructor(props) {
super(props);
this.state = {
mentionComplete: false,
sections: [],
};
}
componentWillReceiveProps(nextProps) {
const {matchTerm} = nextProps;
if ((matchTerm !== this.props.matchTerm && matchTerm === null) || this.state.mentionComplete) {
// if the term changes but is null or the mention has been completed we render this component as null
this.setState({
mentionComplete: false,
sections: [],
});
this.props.onResultCountChange(0);
}
}
completeMention = (day) => {
const mention = day.dateString;
const {cursorPosition, onChangeText, value} = this.props;
const mentionPart = value.substring(0, cursorPosition);
const flags = mentionPart.match(ALL_SEARCH_FLAGS_REGEX);
const currentFlag = flags[flags.length - 1];
let completedDraft = mentionPart.replace(DATE_MENTION_SEARCH_REGEX, `${currentFlag} ${mention} `);
if (value.length > cursorPosition) {
completedDraft += value.substring(cursorPosition);
}
onChangeText(completedDraft, true);
this.props.onResultCountChange(1);
this.setState({mentionComplete: true});
};
render() {
const {mentionComplete} = this.state;
const {matchTerm, enableDateSuggestion, theme} = this.props;
if (matchTerm === null || mentionComplete || !enableDateSuggestion) {
// If we are not in an active state or the mention has been completed return null so nothing is rendered
// other components are not blocked.
return null;
}
const currentDate = (new Date()).toDateString();
const calendarStyle = calendarTheme(theme);
return (
<CalendarList
style={styles.calList}
current={currentDate}
pastScrollRange={24}
futureScrollRange={0}
scrollingEnabled={true}
pagingEnabled={true}
hideArrows={false}
horizontal={true}
showScrollIndicator={true}
onDayPress={this.completeMention}
showWeekNumbers={false}
theme={calendarStyle}
keyboardShouldPersistTaps='always'
/>
);
}
}
const getDateFontSize = () => {
let fontSize = 14;
if (Platform.OS === 'ios') {
const {height, width} = Dimensions.get('window');
if (height < 375 || width < 375) {
fontSize = 13;
}
}
return fontSize;
};
const calendarTheme = memoizeResult((theme) => ({
calendarBackground: theme.centerChannelBg,
monthTextColor: changeOpacity(theme.centerChannelColor, 0.8),
dayTextColor: theme.centerChannelColor,
textSectionTitleColor: changeOpacity(theme.centerChannelColor, 0.25),
'stylesheet.day.basic': {
base: {
width: 22,
height: 22,
alignItems: 'center',
},
text: {
marginTop: 0,
fontSize: getDateFontSize(),
fontWeight: '300',
color: theme.centerChannelColor,
backgroundColor: 'rgba(255, 255, 255, 0)',
lineHeight: 23,
},
today: {
backgroundColor: theme.buttonBg,
width: 24,
height: 24,
borderRadius: 12,
},
todayText: {
color: theme.buttonColor,
},
},
}));
const styles = StyleSheet.create({
calList: {
height: 1700,
paddingTop: 5,
},
});

View File

@@ -1,27 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {makeGetMatchTermForDateMention} from 'app/selectors/autocomplete';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import DateSuggestion from './date_suggestion';
function makeMapStateToProps() {
const getMatchTermForDateMention = makeGetMatchTermForDateMention();
return (state, ownProps) => {
const {cursorPosition, value} = ownProps;
const newValue = value.substring(0, cursorPosition);
const matchTerm = getMatchTermForDateMention(newValue);
return {
matchTerm,
theme: getTheme(state),
};
};
}
export default connect(makeMapStateToProps)(DateSuggestion);

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {Component} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {createSelector} from 'reselect';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
@@ -124,6 +124,7 @@ export default class SlashSuggestion extends Component {
renderItem = ({item}) => (
<SlashSuggestionItem
displayName={item.display_name}
description={item.auto_complete_desc}
hint={item.auto_complete_hint}
onPress={this.completeSuggestion}

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
@@ -12,6 +12,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
export default class SlashSuggestionItem extends PureComponent {
static propTypes = {
displayName: PropTypes.string,
description: PropTypes.string,
hint: PropTypes.string,
onPress: PropTypes.func.isRequired,
@@ -26,6 +27,7 @@ export default class SlashSuggestionItem extends PureComponent {
render() {
const {
displayName,
description,
hint,
theme,
@@ -39,7 +41,7 @@ export default class SlashSuggestionItem extends PureComponent {
onPress={this.completeSuggestion}
style={style.row}
>
<Text style={style.suggestionName}>{`/${trigger} ${hint}`}</Text>
<Text style={style.suggestionName}>{`/${displayName || trigger} ${hint}`}</Text>
<Text style={style.suggestionDescription}>{description}</Text>
</TouchableOpacity>
);

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,35 +1,46 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {
BackHandler,
InteractionManager,
Keyboard,
Platform,
StyleSheet,
View,
} from 'react-native';
import {intlShape} from 'react-intl';
import DrawerLayout from 'react-native-drawer-layout';
import {General, WebsocketEvents} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import Drawer from 'app/components/drawer';
import SafeAreaView from 'app/components/safe_area_view';
import {ViewTypes} from 'app/constants';
import tracker from 'app/utils/time_tracker';
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;
const DRAWER_INITIAL_OFFSET = 40;
const DRAWER_LANDSCAPE_OFFSET = 150;
export default class ChannelSidebar extends Component {
export default class ChannelDrawer extends Component {
static propTypes = {
actions: PropTypes.shape({
getTeams: PropTypes.func.isRequired,
handleSelectChannel: PropTypes.func.isRequired,
markChannelAsViewed: PropTypes.func.isRequired,
makeDirectChannel: PropTypes.func.isRequired,
markChannelAsRead: PropTypes.func.isRequired,
setChannelDisplayName: PropTypes.func.isRequired,
setChannelLoading: PropTypes.func.isRequired,
}).isRequired,
@@ -37,18 +48,16 @@ export default class ChannelSidebar extends Component {
children: PropTypes.node,
currentTeamId: PropTypes.string.isRequired,
currentUserId: PropTypes.string.isRequired,
deviceWidth: PropTypes.number.isRequired,
isLandscape: PropTypes.bool.isRequired,
isTablet: PropTypes.bool.isRequired,
intl: PropTypes.object.isRequired,
navigator: PropTypes.object,
teamsCount: PropTypes.number.isRequired,
theme: PropTypes.object.isRequired,
};
static contextTypes = {
intl: intlShape.isRequired,
};
closeHandle = null;
openHandle = null;
swiperIndex = 1;
constructor(props) {
@@ -58,12 +67,9 @@ export default class ChannelSidebar extends Component {
if (props.isLandscape || props.isTablet) {
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
}
this.state = {
show: false,
lockMode: 'unlocked',
openDrawerOffset,
drawerOpened: false,
};
}
@@ -92,7 +98,7 @@ export default class ChannelSidebar extends Component {
}
shouldComponentUpdate(nextProps, nextState) {
const {currentTeamId, deviceWidth, isLandscape, teamsCount} = this.props;
const {currentTeamId, isLandscape, teamsCount} = this.props;
const {openDrawerOffset} = this.state;
if (nextState.openDrawerOffset !== openDrawerOffset || nextState.show !== this.state.show) {
@@ -100,8 +106,8 @@ export default class ChannelSidebar extends Component {
}
return nextProps.currentTeamId !== currentTeamId ||
nextProps.isLandscape !== isLandscape || nextProps.deviceWidth !== deviceWidth ||
nextProps.teamsCount !== teamsCount || this.state.lockMode !== nextState.lockMode;
nextProps.isLandscape !== isLandscape ||
nextProps.teamsCount !== teamsCount;
}
componentWillUnmount() {
@@ -112,8 +118,8 @@ export default class ChannelSidebar extends Component {
}
handleAndroidBack = () => {
if (this.state.drawerOpened && this.refs.drawer) {
this.refs.drawer.closeDrawer();
if (this.refs.drawer && this.refs.drawer.isOpened()) {
this.refs.drawer.close();
return true;
}
@@ -125,8 +131,8 @@ export default class ChannelSidebar extends Component {
};
closeChannelDrawer = () => {
if (this.state.drawerOpened && this.refs.drawer) {
this.refs.drawer.closeDrawer();
if (this.refs.drawer && this.refs.drawer.isOpened()) {
this.refs.drawer.close();
}
};
@@ -135,17 +141,53 @@ export default class ChannelSidebar extends Component {
};
handleDrawerClose = () => {
this.setState({
drawerOpened: false,
});
this.resetDrawer();
Keyboard.dismiss();
if (this.closeHandle) {
InteractionManager.clearInteractionHandle(this.closeHandle);
this.closeHandle = null;
}
};
handleDrawerCloseStart = () => {
if (!this.closeHandle) {
this.closeHandle = InteractionManager.createInteractionHandle();
}
};
handleDrawerOpen = () => {
this.setState({
drawerOpened: true,
});
if (this.state.openDrawerOffset !== 0) {
Keyboard.dismiss();
}
if (this.openHandle) {
InteractionManager.clearInteractionHandle(this.openHandle);
this.openHandle = null;
}
};
handleDrawerOpenStart = () => {
if (!this.openHandle) {
this.openHandle = InteractionManager.createInteractionHandle();
}
};
handleDrawerTween = (ratio) => {
const opacity = (ratio / 2);
EventEmitter.emit('drawer_opacity', opacity);
return {
mainOverlay: {
backgroundColor: this.props.theme.centerChannelBg,
elevation: 3,
opacity,
},
drawerOverlay: {
backgroundColor: ratio ? '#000' : '#FFF',
opacity: ratio ? (1 - ratio) / 2 : 1,
},
};
};
handleUpdateTitle = (channel) => {
@@ -156,56 +198,52 @@ export default class ChannelSidebar extends Component {
this.props.actions.setChannelDisplayName(channelName);
};
openChannelSidebar = () => {
openChannelDrawer = () => {
this.props.blurPostTextBox();
if (this.refs.drawer) {
this.refs.drawer.openDrawer();
if (this.refs.drawer && !this.refs.drawer.isOpened()) {
this.refs.drawer.open();
}
};
selectChannel = (channel, currentChannelId, closeDrawer = true) => {
selectChannel = (channel, currentChannelId) => {
const {
actions,
} = this.props;
const {
handleSelectChannel,
markChannelAsRead,
setChannelLoading,
setChannelDisplayName,
markChannelAsViewed,
} = actions;
tracker.channelSwitch = Date.now();
if (closeDrawer) {
this.closeChannelDrawer();
this.closeChannelDrawer();
InteractionManager.runAfterInteractions(() => {
setChannelLoading(channel.id !== currentChannelId);
}
setChannelDisplayName(channel.display_name);
if (!channel) {
const utils = require('app/utils/general');
const {intl} = this.context;
const unableToJoinMessage = {
id: 'mobile.open_unknown_channel.error',
defaultMessage: "We couldn't join the channel. Please reset the cache and try again.",
};
const erroMessage = {};
utils.alertErrorWithFallback(intl, erroMessage, unableToJoinMessage);
setChannelLoading(false);
return;
}
setChannelDisplayName(channel.display_name);
EventEmitter.emit('switch_channel', channel, currentChannelId);
handleSelectChannel(channel.id);
requestAnimationFrame(() => {
// mark the channel as viewed after all the frame has flushed
markChannelAsRead(channel.id, currentChannelId);
if (channel.id !== currentChannelId) {
markChannelAsViewed(currentChannelId);
}
});
});
};
joinChannel = (channel, currentChannelId) => {
const {intl} = this.context;
joinChannel = async (channel, currentChannelId) => {
const {
actions,
currentTeamId,
currentUserId,
intl,
} = this.props;
const {
@@ -213,60 +251,51 @@ export default class ChannelSidebar extends Component {
makeDirectChannel,
} = actions;
this.closeChannelDrawer();
actions.setChannelLoading(channel.id !== currentChannelId);
const displayValue = {displayName: channel.display_name};
const utils = require('app/utils/general');
setTimeout(async () => {
const displayValue = {displayName: channel.display_name};
const utils = require('app/utils/general');
let result;
if (channel.type === General.DM_CHANNEL) {
result = await makeDirectChannel(channel.id);
let result;
if (channel.type === General.DM_CHANNEL) {
result = await makeDirectChannel(channel.id, false);
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.",
};
utils.alertErrorWithFallback(intl, result.error, dmFailedMessage, displayValue);
}
} else {
result = await joinChannel(currentUserId, currentTeamId, channel.id);
if (result.error || !result.data || !result.data.channel) {
const joinFailedMessage = {
id: 'mobile.join_channel.error',
defaultMessage: "We couldn't join the channel {displayName}. Please check your connection and try again.",
};
utils.alertErrorWithFallback(intl, result.error, joinFailedMessage, displayValue);
}
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.",
};
utils.alertErrorWithFallback(intl, result.error, dmFailedMessage, displayValue);
}
} else {
result = await joinChannel(currentUserId, currentTeamId, channel.id);
if (result.error || (!result.data && !result.data.channel)) {
actions.setChannelLoading(false);
return;
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.",
};
utils.alertErrorWithFallback(intl, result.error, joinFailedMessage, displayValue);
}
}
requestAnimationFrame(() => {
this.selectChannel(result.data.channel || result.data, currentChannelId, false);
});
}, 200);
if (result.error) {
return;
}
this.selectChannel(result.data, currentChannelId);
};
onPageSelected = (index) => {
this.swiperIndex = index;
if (this.swiperIndex === 0) {
this.setState({lockMode: 'locked-open'});
} else {
this.setState({lockMode: 'unlocked'});
}
};
onSearchEnds = () => {
//hack to update the drawer when the offset changes
const {isLandscape, isTablet} = this.props;
if (this.refs.drawer) {
this.refs.drawer._syncAfterUpdate = true; //eslint-disable-line no-underscore-dangle
}
let openDrawerOffset = DRAWER_INITIAL_OFFSET;
if (isLandscape || isTablet) {
openDrawerOffset = DRAWER_LANDSCAPE_OFFSET;
@@ -275,6 +304,10 @@ export default class ChannelSidebar extends Component {
};
onSearchStart = () => {
if (this.refs.drawer) {
this.refs.drawer._syncAfterUpdate = true; //eslint-disable-line no-underscore-dangle
}
this.setState({openDrawerOffset: 0});
};
@@ -290,7 +323,7 @@ export default class ChannelSidebar extends Component {
}
};
renderNavigationView = () => {
renderContent = () => {
const {
navigator,
teamsCount,
@@ -345,7 +378,6 @@ export default class ChannelSidebar extends Component {
onSearchStart={this.onSearchStart}
onSearchEnds={this.onSearchEnds}
theme={theme}
drawerOpened={this.state.drawerOpened}
/>
</View>
);
@@ -361,7 +393,6 @@ export default class ChannelSidebar extends Component {
onPageSelected={this.onPageSelected}
openDrawerOffset={openDrawerOffset}
showTeams={showTeams}
drawerOpened={this.state.drawerOpened}
>
{lists}
</DrawerSwiper>
@@ -370,20 +401,53 @@ export default class ChannelSidebar extends Component {
};
render() {
const {children, deviceWidth} = this.props;
const {lockMode, openDrawerOffset} = this.state;
const {children, isLandscape} = 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 (
<DrawerLayout
drawerLockMode={lockMode}
<Drawer
ref='drawer'
renderNavigationView={this.renderNavigationView}
onDrawerClose={this.handleDrawerClose}
onDrawerOpen={this.handleDrawerOpen}
drawerWidth={deviceWidth - openDrawerOffset}
onOpenStart={this.handleDrawerOpenStart}
onOpen={this.handleDrawerOpen}
onClose={this.handleDrawerClose}
onCloseStart={this.handleDrawerCloseStart}
captureGestures='open'
type={Platform.OS === 'ios' ? 'static' : 'displace'}
acceptTap={true}
acceptPanOnDrawer={false}
disabled={false}
content={this.renderContent()}
tapToClose={true}
openDrawerOffset={openDrawerOffset}
onRequestClose={this.closeChannelDrawer}
panOpenMask={0.2}
panCloseMask={openDrawerOffset}
panThreshold={0.25}
acceptPan={true}
negotiatePan={true}
useInteractionManager={false}
tweenDuration={100}
tweenHandler={this.handleDrawerTween}
elevation={-5}
bottomPanOffset={Platform.OS === 'ios' ? ANDROID_TOP_LANDSCAPE : IOS_TOP_PORTRAIT}
topPanOffset={Platform.OS === 'ios' ? iosTop : androidTop}
styles={{
main: {
shadowColor: '#000000',
shadowOpacity: 0.4,
shadowRadius: 12,
shadowOffset: {
width: -4,
height: 0,
},
},
}}
>
{children}
</DrawerLayout>
</Drawer>
);
}
}

View File

@@ -9,7 +9,6 @@ ShallowWrapper {
currentChannelId="current_channel_id"
displayName="display_name"
fake={false}
isArchived={false}
isChannelMuted={false}
isMyUser={true}
isUnread={true}
@@ -82,7 +81,6 @@ ShallowWrapper {
<ChannelIcon
channelId="channel_id"
isActive={false}
isArchived={false}
isInfo={false}
isUnread={true}
membersCount={1}
@@ -161,7 +159,6 @@ ShallowWrapper {
<ChannelIcon
channelId="channel_id"
isActive={false}
isArchived={false}
isInfo={false}
isUnread={true}
membersCount={1}
@@ -232,7 +229,6 @@ ShallowWrapper {
<ChannelIcon
channelId="channel_id"
isActive={false}
isArchived={false}
isInfo={false}
isUnread={true}
membersCount={1}
@@ -295,7 +291,6 @@ ShallowWrapper {
<ChannelIcon
channelId="channel_id"
isActive={false}
isArchived={false}
isInfo={false}
isUnread={true}
membersCount={1}
@@ -356,7 +351,6 @@ ShallowWrapper {
"props": Object {
"channelId": "channel_id",
"isActive": false,
"isArchived": false,
"isInfo": false,
"isUnread": true,
"membersCount": 1,
@@ -457,7 +451,6 @@ ShallowWrapper {
<ChannelIcon
channelId="channel_id"
isActive={false}
isArchived={false}
isInfo={false}
isUnread={true}
membersCount={1}
@@ -536,7 +529,6 @@ ShallowWrapper {
<ChannelIcon
channelId="channel_id"
isActive={false}
isArchived={false}
isInfo={false}
isUnread={true}
membersCount={1}
@@ -607,7 +599,6 @@ ShallowWrapper {
<ChannelIcon
channelId="channel_id"
isActive={false}
isArchived={false}
isInfo={false}
isUnread={true}
membersCount={1}
@@ -670,7 +661,6 @@ ShallowWrapper {
<ChannelIcon
channelId="channel_id"
isActive={false}
isArchived={false}
isInfo={false}
isUnread={true}
membersCount={1}
@@ -731,7 +721,6 @@ ShallowWrapper {
"props": Object {
"channelId": "channel_id",
"isActive": false,
"isArchived": false,
"isInfo": false,
"isUnread": true,
"membersCount": 1,

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
@@ -38,7 +38,6 @@ export default class ChannelItem extends PureComponent {
type: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
unreadMsgs: PropTypes.number.isRequired,
isArchived: PropTypes.bool.isRequired,
};
static defaultProps = {
@@ -98,23 +97,12 @@ export default class ChannelItem extends PureComponent {
teammateDeletedAt,
theme,
type,
isArchived,
} = this.props;
// Only ever show an archived channel if it's the currently viewed channel.
// It should disappear as soon as one navigates to another channel.
if (isArchived && (currentChannelId !== channelId)) {
return null;
}
if (!this.showChannelAsUnread() && shouldHideChannel) {
return null;
}
if (!this.props.displayName) {
return null;
}
const {intl} = this.context;
let channelDisplayName = displayName;
@@ -173,7 +161,6 @@ export default class ChannelItem extends PureComponent {
teammateDeletedAt={teammateDeletedAt}
theme={theme}
type={type}
isArchived={isArchived}
/>
);
@@ -242,7 +229,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
color: theme.sidebarUnreadText,
},
badge: {
backgroundColor: theme.mentionBg,
backgroundColor: theme.mentionBj,
borderColor: theme.sidebarHeaderBg,
borderRadius: 10,
borderWidth: 1,

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {configure, shallow} from 'enzyme';
@@ -34,7 +34,6 @@ describe('ChannelItem', () => {
sidebarTextHoverBg: '#aaa',
},
unreadMsgs: 1,
isArchived: false,
};
test('should match snapshot', () => {

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
@@ -10,10 +10,9 @@ import {
getMyChannelMember,
shouldHideDefaultChannel,
} from 'mattermost-redux/selectors/entities/channels';
import {getTheme, getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUserId, getUser} from 'mattermost-redux/selectors/entities/users';
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
import {displayUsername} from 'mattermost-redux/utils/user_utils';
import ChannelItem from './channel_item';
@@ -27,15 +26,12 @@ function makeMapStateToProps() {
let isMyUser = false;
let teammateDeletedAt = 0;
let displayName = channel.display_name;
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;
}
const teammateNameDisplay = getTeammateNameDisplaySetting(state);
displayName = displayUsername(teammate, teammateNameDisplay, false);
}
const currentChannelId = getCurrentChannelId(state);
@@ -61,9 +57,10 @@ function makeMapStateToProps() {
if (member && member.notify_props) {
showUnreadForMsgs = member.notify_props.mark_unread !== General.MENTION;
}
return {
currentChannelId,
displayName,
displayName: channel.display_name,
fake: channel.fake,
isChannelMuted: isChannelMuted(member),
isMyUser,
@@ -75,7 +72,6 @@ function makeMapStateToProps() {
theme: getTheme(state),
type: channel.type,
unreadMsgs,
isArchived: channel.delete_at > 0,
};
};
}

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
@@ -29,7 +29,6 @@ export default class ChannelsList extends PureComponent {
onSelectChannel: PropTypes.func.isRequired,
onShowTeams: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
drawerOpened: PropTypes.bool,
};
static contextTypes = {
@@ -49,12 +48,6 @@ export default class ChannelsList extends PureComponent {
});
}
componentWillReceiveProps(nextProps) {
if (!nextProps.drawerOpened && this.props.drawerOpened) {
this.cancelSearch();
}
}
onSelectChannel = (channel, currentChannelId) => {
if (channel.fake) {
this.props.onJoinChannel(channel, currentChannelId);
@@ -251,7 +244,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
lineHeight: 18,
},
above: {
backgroundColor: theme.mentionBg,
backgroundColor: theme.mentionBj,
top: 9,
},
};

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import deepEqual from 'deep-equal';
import React, {Component} from 'react';
@@ -19,7 +19,7 @@ import {General} from 'mattermost-redux/constants';
import {sortChannelsByDisplayName} from 'mattermost-redux/utils/channel_utils';
import {displayUsername} from 'mattermost-redux/utils/user_utils';
import ChannelItem from 'app/components/sidebars/main/channels_list/channel_item';
import ChannelItem from 'app/components/channel_drawer/channels_list/channel_item';
import {ListTypes} from 'app/constants';
const VIEWABILITY_CONFIG = ListTypes.VISIBILITY_CONFIG_DEFAULTS;
@@ -114,7 +114,7 @@ class FilteredList extends Component {
channelId={channel.id}
channel={channel}
isSearchResult={true}
isUnread={channel.isUnread}
isUnread={false}
mentions={0}
onSelectChannel={this.onSelectChannel}
/>
@@ -173,11 +173,8 @@ class FilteredList extends Component {
buildUnreadChannelsForSearch = (props, term) => {
const {unreadChannels} = props.channels;
return this.filterChannels(unreadChannels, term).map((item) => {
item.isUnread = true;
return item;
});
};
return this.filterChannels(unreadChannels, term);
}
buildCurrentDMSForSearch = (props, term) => {
const {channels, teammateNameDisplay, profiles, statuses, pastDirectMessages, groupChannelMemberDetails} = props;

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
@@ -16,13 +16,11 @@ import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {getTheme, getFavoritesPreferences} from 'mattermost-redux/selectors/entities/preferences';
import {showCreateOption} from 'mattermost-redux/utils/channel_utils';
import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import List from './list';
function mapStateToProps(state) {
const config = getConfig(state);
const license = getLicense(state);
const {config, license} = state.entities.general;
const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : '';
const unreadChannelIds = getSortedUnreadChannelIds(state);
const favoriteChannelIds = getSortedFavoriteChannelIds(state);

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
@@ -16,7 +16,7 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import {General} from 'mattermost-redux/constants';
import {debounce} from 'mattermost-redux/actions/helpers';
import ChannelItem from 'app/components/sidebars/main/channels_list/channel_item';
import ChannelItem from 'app/components/channel_drawer/channels_list/channel_item';
import {ListTypes} from 'app/constants';
import {preventDoubleTap} from 'app/utils/tap';
import {changeOpacity} from 'app/utils/theme';
@@ -313,7 +313,7 @@ export default class List extends PureComponent {
emitUnreadIndicatorChange = debounce((showIndicator) => {
if (showIndicator && !UnreadIndicator) {
UnreadIndicator = require('app/components/sidebars/main/channels_list/unread_indicator').default;
UnreadIndicator = require('app/components/channel_drawer/channels_list/unread_indicator').default;
}
this.setState({showIndicator});
}, 100);

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
@@ -9,7 +9,7 @@ import {getCurrentTeam, getMyTeamsCount, getChannelDrawerBadgeCount} from 'matte
import SwitchTeamsButton from './switch_teams_button';
function mapStateToProps(state) {
const team = getCurrentTeam(state) || {};
const team = getCurrentTeam(state);
return {
currentTeamId: team.id,

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import PropTypes from 'prop-types';
import React from 'react';
@@ -116,7 +116,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
fontSize: 14,
},
badge: {
backgroundColor: theme.mentionBg,
backgroundColor: theme.mentionBj,
borderColor: theme.sidebarHeaderBg,
borderRadius: 10,
borderWidth: 1,

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
@@ -17,7 +17,6 @@ export default class DrawerSwiper extends Component {
openDrawerOffset: PropTypes.number,
showTeams: PropTypes.bool.isRequired,
theme: PropTypes.object.isRequired,
drawerOpened: PropTypes.bool,
};
static defaultProps = {
@@ -26,12 +25,9 @@ export default class DrawerSwiper extends Component {
};
shouldComponentUpdate(nextProps) {
const {deviceWidth, openDrawerOffset, showTeams, theme} = this.props;
const {deviceWidth, showTeams, theme} = this.props;
return nextProps.deviceWidth !== deviceWidth ||
nextProps.showTeams !== showTeams ||
nextProps.openDrawerOffset !== openDrawerOffset ||
nextProps.theme !== theme ||
nextProps.drawerOpened !== this.props.drawerOpened;
nextProps.showTeams !== showTeams || nextProps.theme !== theme;
}
runOnLayout = (shouldRun = true) => {

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';

View File

@@ -1,25 +1,24 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {joinChannel} from 'mattermost-redux/actions/channels';
import {joinChannel, markChannelAsRead, markChannelAsViewed} from 'mattermost-redux/actions/channels';
import {getTeams} from 'mattermost-redux/actions/teams';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentTeamId, getMyTeamsCount} from 'mattermost-redux/selectors/entities/teams';
import {setChannelDisplayName, setChannelLoading} from 'app/actions/views/channel';
import {handleSelectChannel, setChannelDisplayName, setChannelLoading} from 'app/actions/views/channel';
import {makeDirectChannel} from 'app/actions/views/more_dms';
import {isLandscape, isTablet, getDimensions} from 'app/selectors/device';
import {isLandscape, isTablet} from 'app/selectors/device';
import MainSidebar from './main_sidebar.js';
import ChannelDrawer from './channel_drawer.js';
function mapStateToProps(state) {
const {currentUserId} = state.entities.users;
return {
...getDimensions(state),
currentTeamId: getCurrentTeamId(state),
currentUserId,
isLandscape: isLandscape(state),
@@ -33,12 +32,15 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getTeams,
handleSelectChannel,
joinChannel,
markChannelAsViewed,
makeDirectChannel,
markChannelAsRead,
setChannelDisplayName,
setChannelLoading,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(MainSidebar);
export default connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(ChannelDrawer);

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
@@ -143,7 +143,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
fontSize: 20,
},
badge: {
backgroundColor: theme.mentionBg,
backgroundColor: theme.mentionBj,
borderColor: theme.sidebarHeaderBg,
borderRadius: 10,
borderWidth: 1,

View File

@@ -1,5 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See License.txt for license information.
import React from 'react';
import PropTypes from 'prop-types';
@@ -25,7 +25,6 @@ export default class ChannelIcon extends React.PureComponent {
teammateDeletedAt: PropTypes.number,
theme: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
isArchived: PropTypes.bool.isRequired,
};
static defaultProps = {
@@ -46,7 +45,6 @@ export default class ChannelIcon extends React.PureComponent {
teammateDeletedAt,
theme,
type,
isArchived,
} = this.props;
const style = getStyleSheet(theme);
@@ -78,14 +76,8 @@ export default class ChannelIcon extends React.PureComponent {
}
let icon;
if (isArchived) {
icon = (
<Icon
name='archive'
style={[style.icon, unreadIcon, activeIcon, {fontSize: size}]}
/>
);
} else if (type === General.OPEN_CHANNEL) {
if (type === General.OPEN_CHANNEL) {
icon = (
<Icon
name='globe'

Some files were not shown because too many files have changed in this diff Show More