Compare commits

..

57 Commits

Author SHA1 Message Date
Elias Nahum
2f9253b753 Bump app build number to 151 (#2276) 2018-10-18 13:21:06 -03:00
Elias Nahum
e23ad78e50 Bump app version number to 1.13.1 2018-10-18 12:42:15 -03:00
Elias Nahum
07fd3605f6 Bump app build number to 150 (#2275) 2018-10-18 12:17:17 -03:00
Elias Nahum
3868929237 Hook the onMessage event only when needed (#2271) 2018-10-18 12:13:13 -03:00
Elias Nahum
e52e482274 Fix typo in build script (#2272) 2018-10-18 10:56:27 -03:00
Elias Nahum
cd29014cc5 Update build script to run npm ci instead of npm install (#2264) 2018-10-18 10:56:16 -03:00
Elias Nahum
afbcc2a203 Bump app build number to 149 (#2262) 2018-10-12 10:24:21 -03:00
Elias Nahum
94dec5e443 Fix uploading iOS videos (#2260) 2018-10-12 17:33:18 +08:00
Elias Nahum
f6e23a9e0c Bump app build number to 148 (#2259) 2018-10-11 17:16:52 -03:00
Elias Nahum
fe1fc259ad Fix crash when loading the permalink view (#2258) 2018-10-11 16:54:16 -03:00
sudheer
e480dcfacb MM-12652 Fix for android thread crash 2018-10-12 00:38:12 +05:30
Elias Nahum
62026e375a Set explicit white background to icon previews (#2256) 2018-10-11 15:32:09 -03:00
sudheer
9c22be4465 MM-12491 Fix archive channels staying in LHS
* Add isArchived flag for all DM's and channels
 * Exclude search results from channel_list component to show
   results when isSearchResult flag is set
   This is to let user search for deactivated users from jumpto
2018-10-11 23:12:57 +05:30
Elias Nahum
b5b948e58f Properly handle max file size (#2248)
* Properly handle max file size

* Feedback review
2018-10-11 14:12:11 -03:00
Martin Kraft
5d9b8a7e06 Updates redux ref. (#2254) 2018-10-11 12:50:03 -04:00
Elias Nahum
2b495f4c51 Change PR build filename to PR-{PR_ID} (#2250) 2018-10-10 21:19:27 -03:00
Elias Nahum
49a284f46d Bump app build number to 147 (#2249) 2018-10-10 16:57:54 -03:00
Sudheer
61a33b3025 MM-12560 Fix for infinite loading indicator for thread from search (#2246)
* MM-12560 Fix for infinite loading indicator for thread from search

* Change tests
2018-10-11 00:00:08 +05:30
Elias Nahum
01d7d08b25 Remove prepare-pr so it does not checkout the branch (#2245) 2018-10-10 09:38:02 -03:00
Elias Nahum
4bd364df42 translations PR 20181009 (#2239) 2018-10-10 08:43:22 -03:00
Martin Kraft
ef808262d7 MM-12552: Adds parameter to include archived channel search results. 2018-10-09 16:12:10 -04:00
Elias Nahum
95f8e72c11 Update Fastlane scripts (#2231)
* Upload builds to store after a successful build

* Update script to build PRs
2018-10-09 12:06:01 -03:00
Elias Nahum
3ea3fe7e2f Fix update keywords field on Android (#2229) 2018-10-09 11:57:16 -03:00
Elias Nahum
2e5f7fd60c Fix KeyboardAvoidView for android (#2226) 2018-10-09 11:55:11 -03:00
Harrison Healey
cb98efc6da MM-11477 Use custom thunk middleware to intercept leaked network errors (#2222)
* MM-11477 Use custom thunk middleware to intercept leaked network errors

* MM-11477 Always include url in Client4 errors

* Update redux
2018-10-09 10:21:45 -04:00
Saturnino Abril
09a49302f2 Add deactivated to separate row on user list and fix truncating user info (#2232) 2018-10-09 20:51:39 +08:00
Saturnino Abril
68fe3653a8 add opacity of 0.3 to circle stroke of offline status (#2233) 2018-10-09 20:48:41 +08:00
Elias Nahum
2cf74c07cb Fix gifs displaying as still images on Android (#2230) 2018-10-08 12:28:17 -03:00
Elias Nahum
a70688fe44 Missing translations on login screen (#2227) 2018-10-08 12:26:54 -03:00
Saturnino Abril
b8f4757300 [MM-12550] Fix hangup on permalink view when user deleted the post in it (#2228)
* fix hangup on permalink view when user deleted the post in it

* return null when no need to change state on getDerivedStateFromProps
2018-10-08 23:16:53 +08:00
Elias Nahum
b3ec19fe17 Fix Option to capture and share video (#2225) 2018-10-05 17:47:55 -03:00
Elias Nahum
faa3a55b3d Reset isLoadingMoreTop flag when switching channels (#2224) 2018-10-05 17:46:24 -03:00
Harrison Healey
dff9628b6e MM-12189 Set autocomplete max height based off available space (#2218)
* MM-12189 Set autocomplete max height based off available space

* Have each autocomplete type determine its own max height since DateSuggestion needs more space

* Remove unused props and ref
2018-10-05 09:58:53 -04:00
Saturnino Abril
82d9995f2b fix issue that blocks adding test for thread screen (#2221) 2018-10-05 21:50:03 +08:00
Elias Nahum
112fd06dfd Revert "image thumbnail not centered (#2210)" and fix it properly (#2219)
This reverts commit 5f31374e74.
2018-10-04 19:42:17 -03:00
Elias Nahum
92541025ef Fix fastlane android script (#2217) 2018-10-03 18:00:21 -03:00
Elias Nahum
d927642cb9 Bump app build number to 146 (#2216) 2018-10-03 16:29:21 -03:00
Elias Nahum
696b2e7c5e Fix search screen from braking and improved how to get more results (#2215)
* Fix search screen from braking and improved how to get more results

* Update mattermost-redux
2018-10-03 16:16:20 -03:00
Harrison Healey
bfac3546c9 MM-12424 Properly set softInputMode on Android activity (#2201) 2018-10-03 16:02:29 -03:00
Elias Nahum
e170bc1177 Fastlane update to include new build process (#2208) 2018-10-03 15:10:24 -03:00
Joram Wilander
b41777a7e0 MM-12348 Persist interactive menu choices past channel switch and share with thread view (#2207)
* Persist interactive menu choices past channel switch and share with thread view

* Remove unneeded async
2018-10-03 15:08:35 -03:00
Sudheer
783dc66b2a MM-12301 Add padding for error text on edit profile screen (#2214) 2018-10-03 15:07:12 -03:00
Elias Nahum
1c093f70a4 image thumbnail not centered (#2210)
* image thumbnail not centered

* feedback review
2018-10-03 15:05:22 -03:00
Martin Kraft
63bcdc42fb MM-12061: Fixes component name typo and adds missing param. (#2199) 2018-10-03 15:05:07 -03:00
Saturnino Abril
793aea617c fix empty space on link preview when title and URL is not present in open graph meta (#2197) 2018-10-03 21:48:05 +08:00
Elias Nahum
ed3e822b63 Draft channel indicator to ignore message with whitespaces only (#2209) 2018-10-03 09:51:20 -03:00
Elias Nahum
7bbe6ccd9f translations PR 20181001 (#2204) 2018-10-02 18:38:47 -03:00
Harrison Healey
bc159153d6 MM-12424 Remove unused props from KeyboardLayout and simplify methods (#2203)
* MM-12424 Remove unused props from KeyboardLayout and simplify methods

* Update snapshots
2018-10-02 17:13:04 -04:00
sudheer
167b91d7fd MM-12347 Fix crash of app when accessing jumpTo 2018-10-02 22:51:50 +05:30
Saturnino Abril
87b3e3e724 fix empty reaction row when profile is missing (#2196) 2018-10-01 18:41:14 +08:00
Saturnino Abril
cb1e286e4f Update unit test: use default theme, snapshot on wrapped elements and removal of unnecessary adapter (#2171)
* use default theme when unit testing the component

* update snapshots by getting wrapped elements only

* fix merge conflicts
2018-10-01 15:12:10 +08:00
Saturnino Abril
fb2f711359 fix blank space on link preview when no site name (#2193) 2018-10-01 15:07:49 +08:00
Saturnino Abril
07ff66726c add border to image of link preview and not allow such image width to exceed the view port width (#2189) 2018-10-01 09:39:49 +08:00
Elias Nahum
2bb69a9b7e Bump App build number to 145 (#2195) 2018-09-28 17:33:17 -03:00
Elias Nahum
a25423eb4b Fastlane (#2190)
* Have fastlane use BRANCH_TO_BUILD

* Update fastlane
2018-09-28 14:49:18 -03:00
Christopher Speller
dfb0557de6 Updating redux 2018-09-28 10:16:09 -07:00
Elias Nahum
093484eab3 Do not show a loading spinner if thread has root post loaded (#2186)
* Do not show a loading spinner if thread has root post loaded

* Feedback review

* Remove additional condition
2018-09-28 12:12:45 -03:00
1011 changed files with 32133 additions and 66428 deletions

View File

@@ -1,23 +0,0 @@
version: 2.1
jobs:
test:
working_directory: ~/mattermost-mobile
docker:
- image: circleci/node:10
steps:
- checkout
- run: |
echo assets/base/config.json
cat assets/base/config.json
# Avoid installing pods
touch .podinstall
# Run tests
make test || exit 1
workflows:
version: 2
pr-test:
jobs:
- test

View File

@@ -11,7 +11,7 @@ charset = utf-8
indent_style = space
indent_size = 4
[{package.json,.eslintrc.json}]
[webapp/package.json]
indent_size = 2
[Makefile]

View File

@@ -1,31 +1,276 @@
{
"extends": [
"./node_modules/eslint-config-mattermost/.eslintrc.json",
"./node_modules/eslint-config-mattermost/.eslintrc-react.json"
],
"settings": {
"react": {
"pragma": "React",
"version": "16.5"
}
},
"env": {
"jest": true
},
"globals": {
"__DEV__": true
},
"rules": {
"global-require": 0,
"react/display-name": [2, { "ignoreTranspilerName": false }],
"react/jsx-filename-extension": [2, {"extensions": [".js"]}]
},
"overrides": [
{
"files": ["*.test.js", "*.test.jsx"],
"env": {
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"impliedStrict": true,
"modules": true
}
},
"parser": "babel-eslint",
"settings": {
"react": {
"pragma": "React",
"version": "16.5"
}
},
"plugins": [
"react",
"header"
],
"env": {
"browser": true,
"node": true,
"jquery": true,
"es6": true,
"jest": true
}
},
"globals": {
"after": true,
"afterAll": true,
"afterEach": true,
"before": true,
"beforeAll": true,
"beforeEach": true,
"describe": true,
"expect": true,
"it": true,
"jest": true,
"test": true,
"__DEV__": true
},
"rules": {
"array-bracket-spacing": [2, "never"],
"array-callback-return": 2,
"arrow-body-style": 0,
"arrow-parens": [2, "always"],
"arrow-spacing": [2, { "before": true, "after": true }],
"block-scoped-var": 2,
"brace-style": [2, "1tbs", { "allowSingleLine": false }],
"camelcase": [2, {"properties": "never"}],
"class-methods-use-this": 0,
"comma-dangle": [2, "always-multiline"],
"comma-spacing": [2, {"before": false, "after": true}],
"comma-style": [2, "last"],
"complexity": [0, 10],
"computed-property-spacing": [2, "never"],
"consistent-return": 2,
"consistent-this": [2, "self"],
"constructor-super": 2,
"curly": [2, "all"],
"dot-location": [2, "object"],
"dot-notation": 2,
"eqeqeq": [2, "smart"],
"func-call-spacing": [2, "never"],
"func-names": 2,
"func-style": [2, "declaration", { "allowArrowFunctions": true }],
"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"],
"key-spacing": [2, {"beforeColon": false, "afterColon": true, "mode": "strict"}],
"keyword-spacing": [2, {"before": true, "after": true, "overrides": {}}],
"line-comment-position": 0,
"linebreak-style": 2,
"lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true, "allowBlockEnd": true }],
"max-lines": [1, {"max": 450, "skipBlankLines": true, "skipComments": false}],
"max-nested-callbacks": [2, {"max":2}],
"max-statements-per-line": [2, {"max": 1}],
"multiline-ternary": [1, "never"],
"new-cap": 2,
"new-parens": 2,
"newline-before-return": 0,
"newline-per-chained-call": 0,
"no-alert": 2,
"no-array-constructor": 2,
"no-caller": 2,
"no-case-declarations": 2,
"no-class-assign": 2,
"no-cond-assign": [2, "except-parens"],
"no-confusing-arrow": 2,
"no-console": 2,
"no-const-assign": 2,
"no-constant-condition": 2,
"no-debugger": 2,
"no-div-regex": 2,
"no-dupe-args": 2,
"no-dupe-class-members": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-duplicate-imports": [2, {"includeExports": true}],
"no-else-return": 2,
"no-empty": 2,
"no-empty-function": 2,
"no-empty-pattern": 2,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-label": 2,
"no-extra-parens": 0,
"no-extra-semi": 2,
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-global-assign": 2,
"no-implicit-coercion": 2,
"no-implicit-globals": 0,
"no-implied-eval": 2,
"no-inner-declarations": 0,
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-lonely-if": 2,
"no-loop-func": 2,
"no-magic-numbers": 0,
"no-mixed-operators": [2, {"allowSamePrecedence": false}],
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": [2, { "exceptions": { "Property": false } }],
"no-multi-str": 0,
"no-multiple-empty-lines": [2, {"max": 1}],
"no-native-reassign": 2,
"no-negated-condition": 2,
"no-nested-ternary": 2,
"no-new": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-symbol": 2,
"no-new-wrappers": 2,
"no-octal-escape": 2,
"no-param-reassign": 2,
"no-process-env": 2,
"no-process-exit": 2,
"no-proto": 2,
"no-redeclare": 2,
"no-return-assign": [2, "always"],
"no-script-url": 2,
"no-self-assign": [2, {"props": true}],
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow": [2, {"hoist": "functions"}],
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-tabs": 0,
"no-template-curly-in-string": 2,
"no-ternary": 0,
"no-this-before-super": 2,
"no-throw-literal": 0,
"no-trailing-spaces": [2, { "skipBlankLines": false }],
"no-undef-init": 2,
"no-undefined": 2,
"no-underscore-dangle": 2,
"no-unexpected-multiline": 2,
"no-unmodified-loop-condition": 2,
"no-unneeded-ternary": [2, {"defaultAssignment": false}],
"no-unreachable": 2,
"no-unsafe-finally": 2,
"no-unsafe-negation": 2,
"no-unused-expressions": 2,
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
"no-use-before-define": [2, {"classes": false, "functions": false, "variables": false}],
"no-useless-computed-key": 2,
"no-useless-concat": 2,
"no-useless-constructor": 2,
"no-useless-escape": 2,
"no-useless-rename": 2,
"no-var": 0,
"no-void": 2,
"no-warning-comments": 1,
"no-whitespace-before-property": 2,
"no-with": 2,
"object-curly-newline": 0,
"object-curly-spacing": [2, "never"],
"object-property-newline": [2, {"allowMultiplePropertiesPerLine": true}],
"object-shorthand": [2, "always"],
"one-var": [2, "never"],
"one-var-declaration-per-line": 0,
"operator-linebreak": [2, "after"],
"padded-blocks": [2, "never"],
"prefer-arrow-callback": 2,
"prefer-const": 2,
"prefer-numeric-literals": 2,
"prefer-reflect": 2,
"prefer-rest-params": 2,
"prefer-spread": 2,
"prefer-template": 0,
"quote-props": [2, "as-needed"],
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"react/display-name": [2, { "ignoreTranspilerName": false }],
"react/jsx-boolean-value": [2, "always"],
"react/jsx-closing-bracket-location": [2, { "location": "tag-aligned" }],
"react/jsx-curly-spacing": [2, "never"],
"react/jsx-equals-spacing": [2, "never"],
"react/jsx-filename-extension": [2, {"extensions": [".js"]}],
"react/jsx-first-prop-new-line": [2, "multiline"],
"react/jsx-handler-names": 0,
"react/jsx-indent": [2, 4],
"react/jsx-indent-props": [2, 4],
"react/jsx-key": 2,
"react/jsx-max-props-per-line": [2, { "maximum": 1 }],
"react/jsx-no-bind": 0,
"react/jsx-no-duplicate-props": [2, { "ignoreCase": false }],
"react/jsx-no-literals": 2,
"react/jsx-no-target-blank": 2,
"react/jsx-no-undef": 2,
"react/jsx-pascal-case": 2,
"react/jsx-tag-spacing": [2, {"closingSlash": "never", "beforeSelfClosing": "never", "afterOpening": "never"}],
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/jsx-no-comment-textnodes": 2,
"react/no-danger": 0,
"react/no-deprecated": 1,
"react/no-did-mount-set-state": 2,
"react/no-did-update-set-state": 2,
"react/no-direct-mutation-state": 2,
"react/no-is-mounted": 2,
"react/no-multi-comp": [2, { "ignoreStateless": true }],
"react/no-render-return-value": 2,
"react/no-set-state": 0,
"react/no-string-refs": 0,
"react/no-unknown-property": 2,
"react/prefer-es6-class": 2,
"react/prefer-stateless-function": 0,
"react/prop-types": 2,
"react/require-optimization": 1,
"react/require-render-return": 2,
"react/self-closing-comp": 2,
"react/sort-comp": 0,
"react/jsx-wrap-multilines": 2,
"react/no-find-dom-node": 1,
"react/forbid-component-props": 0,
"react/no-danger-with-children": 2,
"react/no-unused-prop-types": [1, {"skipShapeProps": true}],
"react/style-prop-object": 2,
"react/no-children-prop": 2,
"react/no-unescaped-entities": 2,
"require-yield": 2,
"rest-spread-spacing": [2, "never"],
"semi": [2, "always"],
"semi-spacing": [2, {"before": false, "after": true}],
"sort-imports": 0,
"sort-keys": 0,
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, {"anonymous": "never", "named": "never", "asyncArrow": "always"}],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"symbol-description": 2,
"template-curly-spacing": [2, "never"],
"valid-typeof": [2, {"requireStringLiterals": false}],
"vars-on-top": 0,
"wrap-iife": [2, "outside"],
"wrap-regex": 2,
"yoda": [2, "never", {"exceptRange": false, "onlyEquality": false}]
}
]
}

View File

@@ -67,4 +67,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
[version]
^0.92.0
^0.78.0

3
.gitignore vendored
View File

@@ -1,10 +1,8 @@
assets/override
dist
build-ios
*.zip
server.PID
mattermost.keystore
tmp/
# OSX
#
@@ -82,7 +80,6 @@ ios/sentry.properties
# Testing
.nyc_output
coverage
# Pods
.podinstall

View File

@@ -1,489 +1,5 @@
# Mattermost Mobile Apps Changelog
## 1.23.0 Release
- Release Date: September 16, 2019
- Server Versions Supported: Server v5.9+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Bug Fixes
- Fixed an issue where some Giphy actions were not working in ephemeral posts on mobile.
- Fixed an issue where users were unable to create new channels when "Combine all channel types" was selected.
- Fixed an issue on Android EMM where a crash occurred when tapping **Go to Settings**.
- Fixed an issue on iOS where the in-app "Date" localization persisted after server and user changed.
- Fixed an issue where the download step was showing when previewing a video right after posting it.
- Fixed an issue on Android where cancelling a video download twice in a row showed an error.
- Fixed an issue where file attachment thumbnail/preview could fail to load and not be able to be reloaded.
- Fixed an issue on Android where **Channel > Add Members > ADD** text changed to black.
- Fixed an issue on iOS where the **Cancel** label text didn't fit in one line in German language.
- Fixed an issue where longer than allowed reply posts kept showing a warning with every backspace.
- Fixed an issue where there was a delay in search box and emoji content width change when switching to/from portrait/landscape view.
- Fixed an issue where deactivated users did not appear in the "Jump to..." screen.
- Fixed an issue where "@undefined has joined the channel" was shown instead of "Someone has joined the channel" when a user joined a channel that another user was viewing.
- Fixed an issue on Android where the reply arrow was cut off in search results.
- Fixed an issue where changing display theme from webapp didn't work properly on mobile.
- Fixed an issue on iOS where a bot account icon style was broken.
- Fixed an issue with an incorrect UI text for location of touch ID setting.
### Known Issues
- App slows down when opening a channel with large number of animated emoji. [MM-15792](https://mattermost.atlassian.net/browse/MM-15792)
- When users are deactivated, they are not immediately removed from the mention auto-complete. [MM-17953](https://mattermost.atlassian.net/browse/MM-17953)
## 1.22.1 Release
- Release Date: August 23, 2019
- Server Versions Supported: Server v5.9+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Bug Fixes
- Fixed an issue where the apps crashed when setting the language to Chinese Traditional.
- Fixed an issue on Android where push notification receipt delivery failed due to invalid server URL.
- Fixed an issue where the apps crashed when launched via a notification.
- Fixed an issue where posts made while the app was closed did not appear until refresh.
## 1.22.0 Release
- Release Date: August 16, 2019
- Server Versions Supported: Server v5.9+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Highlights
#### Support for iOS13 and Android Q
- Added support for iOS13 and Android Q which are to be released later this year.
### Improvements
- Added support for Interactive Dialog with no elements.
- Added a setting for tablets to enable or disable fixed sidebar.
- Changed "about" section references to use the site name when it is configured in **System Console > Custom Branding > Site Name**.
- Added support for plus-sign and period/dot in custom URL schemes.
- Added "Edit profile" button to right-hand side menu and to users' own profile pop-over.
- Message draft is now saved when closing the app.
- Removing a link preview on webapp now also removes it on the mobile app.
- Added ability to select and copy channel header text and purpose.
### Bug Fixes
- Fixed a few mobile app crash / fatal error issues.
- Fixed an issue where timestamps were off on Android.
- Fixed an issue where contents of ephemeral posts from /giphy were not being displayed on mobile.
- Fixed an issue where team/channel page dots at the bottom of left-hand side overlapped with the last Direct Message channel.
- Fixed an issue where network reconnection incorrectly showed refreshing messages failed.
- Fixed an issue with the channel sidebar theme colors not being respected on iPhone X.
- Fixed an issue where "Message failed to send" had incorrect app badge behaviour.
- Fixed an issue where a white screen was briefly shown after pressing "Send Message" when viewing a user's profile.
- Fixed an issue on Android where using "Https" instead of "https" in the url of an image didn't show the preview.
- Fixed an issue where the client ``setCSRFFromCookie`` did not look for subpaths when accessing cookies.
- Fixed an issue where archived teams reappeared in selector.
- Fixed an issue where users' profile picture and name did not get updated after websocket disconnect.
### Known Issues
- App slows down when opening a channel with large number of animated emoji. [MM-15792](https://mattermost.atlassian.net/browse/MM-15792)
- Some Giphy actions do not work in ephemeral posts. [MM-17842](https://mattermost.atlassian.net/browse/MM-17842)
## 1.21.2 Release
- Release Date: August 1, 2019
- Server Versions Supported: Server v5.9+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Bug Fixes
- Fixed an issue where the mobile apps logged out without a session expiry notification.
## 1.21.1 Release
- Release Date: July 22, 2019
- Server Versions Supported: Server v5.9+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Bug Fixes
- Fixed an issue on Android where logging in using SSO failed when the Mattermost server was running on a subpath.
## 1.21.0 Release
- Release Date: July 16, 2019
- Server Versions Supported: Server v5.9+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Bug Fixes
- Fixed a few mobile app crash / fatal error issues.
- Fixed an issue where having the sidebar open at all times on tablets did not work for split view.
- Fixed an issue where new messages were often hidden behind a keyboard or text field.
- Fixed an issue on Android where channel sorting didn't match the web app.
- Fixed an issue where sharing a GIF via keyboard resulted in an error screen.
- Fixed an issue where long-press menu could not be dragged up when rotating the device to landscape view while the menu was open.
- Fixed an issue on Android where push notification settings were only saved after closing the settings page.
- Fixed an issue where users on View Members list had an icon that appeared to be selectable but was not.
- Fixed an issue where "Jump To" showed archived channels the user did not belong to instead of the ones the user was a member of.
- Fixed an issue where changing the timezone setting manually to "Set automatically" did not work on the mobile app.
- Fixed an issue where setting a position field for AD/LDAP sync or SAML in the System Console did not block the user from changing it in account settings.
- Fixed an issue where **Channel Info > Manage/View Members** screen didn't load channel users.
- Fixed an issue where enabling large fonts on iOS caused the left-hand side text to be cut off.
- Fixed an issue on Android where users could not reply to a push notification if the mention was in a thread message.
### Known Issues
- (Android) On subpath server, logging in using GitLab or OneLogin fails to display Mattermost. [MM-16829](https://mattermost.atlassian.net/browse/MM-16829)
- Buttons inside ephemeral posts are not clickable / functional on the mobile app. [MM-15084](https://mattermost.atlassian.net/browse/MM-15084)
- Android apps slow down when opening a channel with large number of animated emoji. [MM-15792](https://mattermost.atlassian.net/browse/MM-15792)
## 1.20.2 Release
- Release Date: July 10, 2019
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Bug Fixes
- Fixed an issue where Moto G7 devices were detected as tablets and showed a fixed width sidebar.
- Fixed an issue where having the sidebar open at all times on tablets did not work on split view.
## 1.20.1 Release
- Release Date: June 21, 2019
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Bug Fixes
- Fixed an issue where some Android devices were crashing.
- Fixed an issue where messages were missing after reconnecting the network.
## 1.20.0 Release
- Release Date: June 16, 2019
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Highlights
#### Tablet Improvements
- Channel sidebar now remains open at a fixed width on tablet devices.
#### iOS Keyboard Dismissal
- If the keyboard is open, swiping down past it now closes it.
#### Profile Telemetry for Android Beta Builds
- To improve Android app performance, we are collecting trace events and device information, collectively known as metrics, to identify slow performing key areas. Those metrics will be sent only from users using Android app beta build starting in version v1.20, who are logged in to servers that allow sending [diagnostic information](https://docs.mattermost.com/administration/config-settings.html#enable-diagnostics-and-error-reporting).
### Improvements
- Increased the double tap delay for post action buttons.
- Implemented assets for Adaptive icons.
- Users are now brought to the bottom of the channel when posting a message.
- Users can now execute actions while the keyboard is open.
- Added support on iOS for IPv6 on LTE networks.
- Added support for LDAP Group constrained feature with v5.12 servers.
### Bug Fixes
- Fixed an issue where a post wasn't immediately removed when deleting another user's post.
- Fixed an issue where the cursor jumped back when typing after auto-completing a slash command.
- Fixed an issue where the iOS app didnt properly restore its connection after disconnect.
- Fixed an issue where the long press menu persisted after returning from a thread.
- Fixed an issue on Android where the "Write to [channel name]" was cut off for group messages with several users.
- Fixed an issue where users were not able to flag or unflag posts in a read-only channel.
- Fixed an issue where the progress indicator was negative while downloading a video.
- Fixed an issue where the edit post modal didnt have an autocorrect.
- Fixed an issue where the 'I forgot my password' option was available on the mobile client even with Email Authentication disabled on the server.
- Fixed an issue with large separation between placeholders on iPad when a channel was loading.
- Fixed an issue where "Show More" was not removed after the post was edited to a single line.
### Known Issues
- Buttons inside ephemeral posts are not clickable / functional on the mobile app. [MM-15084](https://mattermost.atlassian.net/browse/MM-15084)
- App slows down when opening a channel with large number of animated emoji. [MM-15792](https://mattermost.atlassian.net/browse/MM-15792)
## 1.19.0 Release
- Release Date: May 16, 2019
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Bug Fixes
- Fixed an issue where Android managed config was lost on the thread view.
- Fixed an issue where contents of ephemeral posts did not display on the mobile app.
- Fixed a few mobile app crash / fatal error issues.
- Fixed an issue with an expanding animation when tapping on Jump to Channel in the channel list.
- Fixed an issue on iOS where animated custom emoji weren't animated.
- Fixed an issue on iOS where users were unable to create channel name of 2 characters.
- Fixed an issue on iOS where emoji appeared too close, with uneven spacing, and too small in the info modal.
- Added an error handler when sharing text that was over server's maximum post size with the iOS Share Extension.
- Fixed an issue where users could upload a GIF as a profile image.
### Known Issues
- Buttons inside ephemeral posts are not clickable / functional on the mobile app.
## 1.18.1 Release
- Release Date: April 18, 2019
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Bug Fixes
- Fixed a crash issue caused by a malformed post textbox localize string.
- Fixed an issue where iOS crashed when trying to log in using SSO and the SSO provider set a cookie without an expiration date.
## 1.18.0 Release
- Release Date: April 16, 2019
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
- ``Bot`` tags were added for bot accounts feature in server v5.10 and mobile v1.18, meaning that mobile v1.17 and earlier don't support the tags.
### Highlights
- Added support for Office365 single sign-on (SSO).
- Added support for Integrated Windows Authentication (IWA).
### Improvements
- Added the ability for channel links to open inside the app.
- Added ability for emojis and hyperlinks to render in the message attachment title.
- Added Chinese support for words that trigger mentions.
- Added a setting to the system console to change the minimum length of hashtags.
- Added a reply option to long press context menu.
### Bug Fixes
- Fixed an issue where blank spaces broke markdown tables.
- Fixed an issue where deactivated users appeared on "Add Members" modal but not on the search results.
- Fixed an issue on Android where extra text in the search box appeared after using the autocomplete drop-down.
- Fixed an issue with multiple text entries when typing with Shift+Letter on Android.
- Fixed an issue where push notifications badges did not always clear when read on another device.
- Fixed an issue where opening a single or group notification did not take the user into the channel where the notification came from.
- Fixed an issue where timezone did not automatically update on Android when travelling to another timezone.
- Fixed an issue where the user mention autocomplete drop-down was case sensitive.
- Fixed an issue where system admininistrators were able to see the full long press menu when long pressing a system message.
- Fixed an issue where users were not able to unflag posts from "Flagged Posts" when opened from a read-only channel.
- Fixed an issue where users were unable to create channel names of 2 byte characters.
### Known Issues
- Content for ephemeral messages is not displayed on Mattermost Mobile Apps.
## 1.17.0 Release
- Release Date: March 20, 2019
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- If **DisableLegacyMfa** setting in ``config.json`` is set to ``true`` and [multi-factor authentication](https://docs.mattermost.com/deployment/auth.html) is enabled, ensure your users have upgraded to mobile app version 1.17 or later. See [Important Upgrade Notes](https://docs.mattermost.com/administration/important-upgrade-notes.html) for more details.
- If you are using an EMM provider via AppConfig, make sure to add two new settings, `useVPN` and `timeoutVPN`, to your AppConfig file. The settings were added for EMM connections using VPN on-demand - one to indicate if every request should wait for the VPN connection to be established, and another to set the timeout in seconds. See docs for more details on [setting AppConfig values](https://docs.mattermost.com/mobile/mobile-appconfig.html#mattermost-appconfig-values) for VPN support.
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
- iPhone 5s devices and later with iOS 11+ is required.
### Highlights
- iOS Share Extension now supports large file sizes and improved performance
### Bug Fixes
- Fixed support for EMM connections using VPN on-demand. See docs for more details on [setting AppConfig values](https://docs.mattermost.com/mobile/mobile-appconfig.html#mattermost-appconfig-values) for VPN support.
- Fixed several Android app crash / fatal error issues.
- Fixed an issue on Android where the app crashed intermittently when selecting a link.
- Fixed an issue where email notifications setting was out of sync with the webapp until the setting was edited.
- Fixed an issue where notification badges were not cleared from other clients when clicking on a push notification after opening the mobile app.
- Fixed an issue where the app did not show local notification when session expired.
- Fixed an issue where the profile picture for webhooks was showing the hook owner picture.
- Fixed an issue where some emoji were not rendered as jumbo.
- Fixed an issue where jumbo emoji posted as a reply sometimes appeared with large space beneath.
- Fixed an issue where the "No Internet Connection" banner did not always display when internet connectivity was lost.
- Fixed an issue where the "No Internet Connection" banner did not always disappear when connection was re-estabilished.
- Fixed an issue where opening channels with unreads had loading indicator placed above unread messages line.
## 1.16.1 Release
- Release Date: February 21, 2019
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
### Bug Fixes
- Fixed an issue where link previews and reactions weren't displayed when post metadata was disabled.
- Fixed an issue on Android where the app crashed when sharing multiple files.
## 1.16.0 Release
- Release Date: February 16, 2019
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
### Improvements
- Added the ability to remove own profile picture.
- Changed "X" to "Cancel" on Edit Profile page.
- Added support for relative permalinks.
### Bug Fixes
- Fixed an issue where the iOS app did not wait until the on-demand VPN connection was established. (EMM Providers)
- Fixed an issue with a white screen caused by missing Russian translations.
- Fixed an issue where the iOS badge notification did not always clear.
- Fixed an issue where the thread view displayed a new message indicator.
- Fixed an issue where quick multiple taps on the file icon opened multiple file previews.
- Fixed an issue where the settings page did not show an option to join other teams.
- Fixed an issue where image previews didn't work after using Delete File Cache.
- Fixed an issue on Android where the notification trigger word modal title was "Send email notifications" instead of "Keywords".
- Fixed an issue where the Webhook icon was misaligned and bottom edges were cut off.
- Fixed an issue on Android where the user was not asked to authenticate to the app first when trying to share a photo, resulting in a white "Share modal" screen with a never-ending loading indicator.
- Fixed an issue on iOS where push notifications were not preserved when opening the app via the Mattermost icon.
## 1.15.2 Release
- Release Date: January 16, 2019
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
### Bug Fixes
- Fixed an issue where the status changes for other users did not always stay current in the mobile app.
- Fixed an issue where a post did not fail properly when the user attempted to send the post while there was no network access.
- Fixed an issue where date separators did not update when changing timezones.
- Fixed an issue where the Favorites section did not clear from a users's channel drawer.
- Removed an extra divider below "Edit Channel" of Direct Message Channel Info.
- Fixed an issue where a user was not returned to previously viewed channel after viewing and then closing an archived channel.
- Fixed an issue where a quick double tap on switch of Channel Info created and extra on/off state.
- Fixed an issue where iOS long press menu didn't have rounded corners.
## 1.15.1 Release
- Release Date: December 28, 2018
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
### Bug Fixes
- Fixed an issue preventing some users from logging in using OKTA.
## 1.15.0 Release
- Release Date: December 16, 2018
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
### Compatibility
- Mobile App v1.13+ is required for Mattermost Server v5.4+.
- Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html).
### Highlights
- Added mention and reply mention highlighting.
- Added a sliding animation for the reaction list.
- Added support for pinned posts.
- Added support for jumbo emojis.
- Added support for interactive dialogs.
- Improved UI for the long press menu and emoji reaction viewer.
### Improvements
- Added the ability to include custom headers with requests for custom builds.
- Push Notifications that are grouped by channels are cleared once the channel is read.
- Improved auto-reconnect when unable to reach the server.
- Added support for changing the mobile client status to offline when the app loses connection.
- Added 'View Members' button to archived channels.
- Added support on iOS for keeping the postlist in place without scrolling when new content is available.
### Bug Fixes
- Fixed an issue where clicking on a file did not show downloading progress.
- Fixed an issue on Android where on fresh install the share extension would not properly show available channels.
- Fixed an issue where recently archived channels remained in in: autocomplete when they had been archived.
- Fixed an issue where text should render when no actual custom emoji matched the named emoji pattern.
- Fixed an issue on iOS where text got cut-off after replying to a message.
- Fixed an issue where search modifier for channels was showing Direct Messages without usernames.
- Fixed an issue where "Close Channel" did not work properly when viewing two archived channels in a row.
- Fixed an issue with "Critical Error" screen when trying to upload certain file types from "+" to the left of message input box.
## 1.14.0 Release
- Release Date: November 16, 2018
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device
**Compatibility Note: Mobile App v1.13+ is required for Mattermost Server v5.4+**
### Bug Fixes
- Fixed an issue where the Android app did not allow establishing a network connection with any server that used a self-signed certificate that had the CA certificate user installed on the device.
- Removed "Copy Post" option on long-press message menu for posts without text.
- Fixed an issue where the "Search Results" header was not fully scrolled to top on search "from:username".
- Fixed an issue where channel names truncated at fewer characters than necessary.
- Fixed an issue where the same uploaded photo generated a different file size.
- Fixed an issue where the "(you)" was not displayed to the right of a user's name in the channel drawer when a user opened a Direct Message channel with themself.
- Fixed an issue where a dark theme set from webapp broke mobile display.
- Fixed an issue where channel drawer transition sometimes lagged.
- Fixed an issue where sending photos to Mattermost created large files.
- Fixed an issue where the apps showed "Select a Team" screen when opened.
- Fixed an issue where at-mention, emoji, and slash command autocompletes had a double top border.
- Fixed an issue where the drawer was unable to close when showing the team list.
- Fixed an issue where team sidebar showed + sign even without more teams to join.
## 1.13.1 Release
- Release Date: October 18, 2018
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported
**Compatibility Note: Mobile App v1.13+ is required for Mattermost Server v5.4+**
### Bug Fixes
- Fixed an issue preventing some users from authenticating using OKTA
## v1.13.0 Release
- Release Date: October 16, 2018
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported
**Compatibility Note: Mobile App v1.13+ is required for Mattermost Server v5.4+**
### Highlights
#### View Emoji Reactions
- Hold down on any emoji reaction to see who reacted to the post.
#### Hashtags
- Added support for searching for hashtags in posts.
#### Dropdown menus
- Added support for dropdown menus in message attachments.
### Improvements
- Added support for iPhone XR, XS and XS Max.
- Added support for nicknames on user profile.
- On servers 5.4+, added support for searching in direct and group message channels using the "in:" modifier.
- Channel autocomplete now gets closed if multiple tildes are typed.
- Added a draft icon in sidebar and channel switcher for channels with unsent messages.
- Users are now redirected to the archived channel view (rather than to Town Square) when a channel is archived.
- When closing an archived channel, users are now returned to the previously viewed channel.
### Bug Fixes
- Refactored postlist to include Android Pie fixes and smoother scrolling.
- Fixed an issue where deactivated users were not marked as such in "Jump To" search.
- Fixed an issue where users got a permission error when trying to open a file from within the image preview screen.
- Fixed an issue where session expiry notifications were not being sent on Android.
- Fixed an issue where post attachments failed to upload.
- Fixed an issue where the "DM More..." list cut off user info.
- Fixed an issue where the user would briefly see a system message when loading a reply thread.
- Fixed an issue where the error message was incorrectly formatted if the login method was set to email/password and the user tried to log in with SAML.
- Fixed an issue on Android where the keyboard sometimes overlapped the bottom of the post textbox.
- Fixed an issue where there was no option to take video via "+" > "Take Photo or Video" on iOS.
## v1.12.0 Release
- Release Date: September 16, 2018
- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported

View File

@@ -2,4 +2,33 @@
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.
When you submit a pull request, it goes through a [code review process outlined here](https://developers.mattermost.com/contribute/getting-started/code-review/).
### Review Process for this Repo
After following the steps in the [Contribution Guide](http://docs.mattermost.com/developer/contribution-guide.html), submitted pull requests go through the review process outlined below. We aim to start reviewing pull requests in this repo the week they are submitted, but the length of time to complete the process will vary depending on the pull request.
The one exception may be around release time, where the review process may take longer as the team focuses on our [release process](https://docs.mattermost.com/process/release-process.html).
#### `Stage 1: PM Review`
A Product Manager will review the pull request to make sure it:
1. Fits with our product roadmap
2. Works as expected
3. Meets UX guidelines
This step is sometimes skipped for bugs or small improvements with a ticket, but always happens for new features or pull requests without a related ticket.
The Product Manager may come back with some bugs or UI improvements to fix before the pull request moves on to the next stage.
#### `Stage 2: Dev Review`
Two developers will review the pull request and either give feedback or `+1` the PR.
Any comments will need to be addressed before the pull request moves on to the last stage.
- PRs that do not follow Style Guides cannot be merged
#### `Stage 3: Ready to Merge`
The review process is complete, and the pull request will be merged.

128
Makefile
View File

@@ -1,9 +1,8 @@
.PHONY: pre-run pre-build clean
.PHONY: check-style
.PHONY: i18n-extract-ci
.PHONY: start stop
.PHONY: run run-ios run-android
.PHONY: build build-ios build-android unsigned-ios unsigned-android ios-sim-x86_64
.PHONY: build build-ios build-android unsigned-ios unsigned-android
.PHONY: build-pr can-build-pr prepare-pr
.PHONY: test help
@@ -11,7 +10,6 @@ POD := $(shell which pod 2> /dev/null)
OS := $(shell sh -c 'uname -s 2>/dev/null')
BASE_ASSETS = $(shell find assets/base -type d) $(shell find assets/base -type f -name '*')
OVERRIDE_ASSETS = $(shell find assets/override -type d 2> /dev/null) $(shell find assets/override -type f -name '*' 2> /dev/null)
MM_UTILITIES_DIR = ../mattermost-utilities
node_modules: package.json
@if ! [ $(shell which npm 2> /dev/null) ]; then \
@@ -77,12 +75,6 @@ post-install:
@# Need to copy custom RNDocumentPicker.m that implements direct access to the document picker in iOS
@cp ./native_modules/RNDocumentPicker.m node_modules/react-native-document-picker/ios/RNDocumentPicker/RNDocumentPicker.m
@# Need to copy custom RNCookieManagerIOS.m that fixes a crash when cookies does not have expiration date set
@cp ./native_modules/RNCookieManagerIOS.m node_modules/react-native-cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m
@# Need to copy custom RNCNetInfo.m that checks for internet connectivity instead of reaching a host by default
@cp ./native_modules/RNCNetInfo.m node_modules/@react-native-community/netinfo/ios/RNCNetInfo.m
@rm -f node_modules/intl/.babelrc
@# Hack to get react-intl and its dependencies to work with react-native
@# Based off of https://github.com/este/este/blob/master/gulp/native-fix.js
@@ -90,18 +82,29 @@ post-install:
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-messageformat/package.json
@sed -i'' -e 's|"./lib/locales": false|"./lib/locales": "./lib/locales"|g' node_modules/intl-relativeformat/package.json
@sed -i'' -e 's|"./locale-data/complete.js": false|"./locale-data/complete.js": "./locale-data/complete.js"|g' node_modules/intl/package.json
@sed -i'' -e "s|super.onBackPressed();|this.moveTaskToBack(true);|g" node_modules/react-native-navigation/android/app/src/main/java/com/reactnativenavigation/controllers/NavigationActivity.java
@sed -i'' -e "s|compile 'com.facebook.react:react-native:0.17.+'|compile 'com.facebook.react:react-native:+'|g" node_modules/react-native-bottom-sheet/android/build.gradle
@if [ $(shell grep "const Platform" node_modules/react-native/Libraries/Lists/VirtualizedList.js | grep -civ grep) -eq 0 ]; then \
sed $ -i'' -e "s|const ReactNative = require('ReactNative');|const ReactNative = require('ReactNative');`echo $\\\\\\r;`const Platform = require('Platform');|g" node_modules/react-native/Libraries/Lists/VirtualizedList.js; \
fi
@sed -i'' -e 's|transform: \[{scaleY: -1}\],|...Platform.select({android: {transform: \[{perspective: 1}, {scaleY: -1}\]}, ios: {transform: \[{scaleY: -1}\]}}),|g' node_modules/react-native/Libraries/Lists/VirtualizedList.js
@./node_modules/.bin/patch-package
start: | pre-run ## Starts the React Native packager server
$(call start_packager)
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start; \
else \
echo React Native packager server already running; \
fi
stop: ## Stops the React Native packager server
$(call stop_packager)
@echo Stopping React Native packager server
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 1 ]; then \
ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9; \
echo React Native packager server stopped; \
else \
echo No React Native packager server running; \
fi
check-device-ios:
@if ! [ $(shell which xcodebuild) ]; then \
@@ -161,7 +164,7 @@ run-android: | check-device-android pre-run prepare-android-build ## Runs the ap
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo Running Android app in development; \
if [ ! -z ${VARIANT} ]; then \
if [ ! -z ${VARIANT} ]; then \
react-native run-android --no-packager --variant=${VARIANT}; \
else \
react-native run-android --no-packager; \
@@ -176,65 +179,69 @@ run-android: | check-device-android pre-run prepare-android-build ## Runs the ap
fi; \
fi
build: | stop pre-build check-style i18n-extract-ci ## Builds the app for Android & iOS
$(call start_packager)
build: | stop pre-build check-style ## Builds the app for Android & iOS
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo; \
fi
@echo "Building App"
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane build
$(call stop_packager)
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
build-ios: | stop pre-build check-style i18n-extract-ci ## Builds the iOS app
$(call start_packager)
build-ios: | stop pre-build check-style ## Builds the iOS app
@if [ $(shell ps -ef | 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
$(call stop_packager)
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
build-android: | stop pre-build check-style i18n-extract-ci prepare-android-build ## Build the Android app
$(call start_packager)
build-android: | stop pre-buid check-style prepare-android-build ## Build the Android app
@if [ $(shell ps -ef | 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
$(call stop_packager)
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
unsigned-ios: stop pre-build check-style ## Build an unsigned version of the iOS app
$(call start_packager)
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo; \
fi
@echo "Building unsigned iOS app"
@cd fastlane && NODE_ENV=production bundle exec fastlane ios unsigned
@mkdir -p build-ios
@cd ios/ && xcodebuild -workspace Mattermost.xcworkspace/ -scheme Mattermost -sdk iphoneos -configuration Release -parallelizeTargets -resultBundlePath ../build-ios/result -derivedDataPath ../build-ios/ CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
@cd ios/ && xcodebuild -workspace Mattermost.xcworkspace/ -scheme Mattermost -sdk iphoneos -configuration Relase -parallelizeTargets -resultBundlePath ../build-ios/result -derivedDataPath ../build-ios/ CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
@cd build-ios/ && mkdir -p Payload && cp -R Build/Products/Release-iphoneos/Mattermost.app Payload/ && zip -r Mattermost-unsigned.ipa Payload/
@mv build-ios/Mattermost-unsigned.ipa .
@cd fastlane && bundle exec fastlane upload_file_to_s3 file:Mattermost-unsigned.ipa os_type:iOS
@rm -rf build-ios/
$(call stop_packager)
ios-sim-x86_64: stop pre-build check-style ## Build an unsigned x86_64 version of the iOS app for iPhone simulator
$(call start_packager)
@echo "Building unsigned x86_64 iOS app for iPhone simulator"
@cd fastlane && NODE_ENV=production bundle exec fastlane ios unsigned
@mkdir -p build-ios
@cd ios/ && xcodebuild -workspace Mattermost.xcworkspace/ -scheme Mattermost -arch x86_64 -sdk iphonesimulator -configuration Release -parallelizeTargets -resultBundlePath ../build-ios/result -derivedDataPath ../build-ios/ ENABLE_BITCODE=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ENABLE_BITCODE=NO
@cd build-ios/Build/Products/Release-iphonesimulator/ && zip -r Mattermost-simulator-x86_64.app.zip Mattermost.app/
@mv build-ios/Build/Products/Release-iphonesimulator/Mattermost-simulator-x86_64.app.zip .
@rm -rf build-ios/
@cd fastlane && bundle exec fastlane upload_file_to_s3 file:Mattermost-simulator-x86_64.app.zip os_type:iOS
$(call stop_packager)
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
unsigned-android: stop pre-build check-style prepare-android-build ## Build an unsigned version of the Android app
$(call start_packager)
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo; \
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
@cd fastlane && bundle exec fastlane upload_file_to_s3 file:Mattermost-unsigned.apk os_type:Android
$(call stop_packager)
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
test: | pre-run check-style ## Runs tests
@npm test
build-pr: | can-build-pr stop pre-build check-style i18n-extract-ci ## Build a PR from the mattermost-mobile repo
$(call start_packager)
build-pr: | can-build-pr stop pre-build check-style ## Build a PR from the mattermost-mobile repo
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo; \
fi
@echo "Building App from PR ${PR_ID}"
@cd fastlane && BABEL_ENV=production NODE_ENV=production bundle exec fastlane build_pr pr:PR-${PR_ID}
$(call stop_packager)
@ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9
can-build-pr:
@if [ -z ${PR_ID} ]; then \
@@ -242,37 +249,6 @@ can-build-pr:
exit 1; \
fi
i18n-extract: ## Extract strings for translation from the source code
npm run mmjstool -- i18n extract-mobile
i18n-extract-ci:
mkdir -p tmp
cp assets/base/i18n/en.json tmp/en.json
mkdir -p tmp/fake-webapp-dir/i18n/
echo '{}' > tmp/fake-webapp-dir/i18n/en.json
npm run mmjstool -- i18n extract-mobile --webapp-dir tmp/fake-webapp-dir --mobile-dir .
diff tmp/en.json assets/base/i18n/en.json
rm -rf tmp
## Help documentation https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
define start_packager
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 0 ]; then \
echo Starting React Native packager server; \
npm start & echo; \
else \
echo React Native packager server already running; \
fi
endef
define stop_packager
@echo Stopping React Native packager server
@if [ $(shell ps -ef | grep -i "cli.js start" | grep -civ grep) -eq 1 ]; then \
ps -ef | grep -i "cli.js start" | grep -iv grep | awk '{print $$2}' | xargs kill -9; \
echo React Native packager server stopped; \
else \
echo No React Native packager server running; \
fi
endef

View File

@@ -7,112 +7,6 @@ NOTICES:
This document includes a list of open source components used in Mattermost Mobile, including those that have been modified.
--------
## @babel/runtime
This product contains 'runtime' by Sebastian McKenzie.
babel's modular runtime helpers
* HOMEPAGE:
* https://github.com/babel/babel/tree/master/packages/babel-runtime
* LICENSE: MIT
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
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-community/async-storage
This product contains 'async-storage' by Krzysztof Borowy.
Asynchronous, persistent, key-value storage system for React Native.
* HOMEPAGE:
* https://github.com/react-native-community/react-native-async-storage#readme
* LICENSE: MIT
MIT License
Copyright (c) 2015-present, Facebook, 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.
---
## @react-native-community/netinfo
This product contains 'netinfo' by Matt Oakes.
React Native Network Info API for iOS & Android
* HOMEPAGE:
* https://github.com/react-native-community/react-native-netinfo#readme
* LICENSE: MIT
MIT License
Copyright (c) 2015-present, Facebook, 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.
---
## analytics-react-native
This product contains 'analytics-react-native' by Javier Alvarez.
@@ -312,37 +206,6 @@ SOFTWARE.
---
## core-js
This product contains 'core-js' by Denis Pushkarev.
Modular standard library for JavaScript.
* HOMEPAGE:
* https://github.com/zloirock/core-js
* LICENSE: Copyright (c) 2014-2019 Denis Pushkarev
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.
---
## deep-equal
This product contains 'deep-equal' by James Halliday.
@@ -375,75 +238,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
## deepmerge
This product contains 'deepmerge' by Josh Duff.
A library for deep (recursive) merging of Javascript objects.
* HOMEPAGE:
* https://github.com/TehShrike/deepmerge
* LICENSE: The MIT License (MIT)
Copyright (c) 2012 James Halliday, Josh Duff, and other contributors
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.
---
## emoji-regex
This product contains 'emoji-regex' by Mathias Bynens.
A regular expression to match all Emoji-only symbols as per the Unicode Standard.
* HOMEPAGE:
* https://mths.be/emoji-regex
* LICENSE: MIT
MIT License
Copyright Mathias Bynens <https://mathiasbynens.be/>
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.
---
## fuse.js
This product contains 'fuse.js' by Kirollos Risk.
@@ -775,7 +569,7 @@ SOFTWARE.
## jsc-android
This product contains 'jsc-android' by React Native Community.
This product contains 'jsc-android' by React Community.
Pre-build version of JavaScriptCore to be used by React Native apps
@@ -823,7 +617,7 @@ Common code (API client, Redux stores, logic, utility functions) for building a
* LICENSE: Apache-2.0
Copyright 2015-present Mattermost, Inc.
Copyright 2016 Mattermost, Inc.
Apache License
Version 2.0, January 2004
@@ -1031,7 +825,7 @@ Copyright 2015-present Mattermost, Inc.
## mime-db
This product contains 'mime-db' by Douglas Christopher Wilson.
This product contains 'mime-db' by GitHub user "jshttp".
Media Type Database
@@ -1212,7 +1006,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## react-native
This product contains 'react-native' by Facebook.
This product contains a modified version of 'react-native' by Facebook.
A framework for building native apps using React
@@ -1245,19 +1039,6 @@ SOFTWARE.
---
## react-native-android-open-settings
This product contains 'react-native-android-open-settings' by Leonardo Velasquez.
Open Android settings from your React Native app
* HOMEPAGE:
* https://github.com/levelasquez/react-native-android-open-settings
* LICENSE: ISC
---
## react-native-animatable
This product contains 'react-native-animatable' by Joel Arvidsson.
@@ -1293,6 +1074,41 @@ SOFTWARE.
---
## react-native-bottom-sheet
This product contains 'react-native-bottom-sheet' by WhatAKitty.
React Native Bottom sheet for android
* HOMEPAGE:
* https://github.com/WhatAKitty/react-native-bottom-sheet#readme
* LICENSE: MIT
The MIT License (MIT)
Copyright (c) 2016 WhatAKitty
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-button
This product contains 'react-native-button' by James Ide.
@@ -1445,7 +1261,7 @@ This product contains a modified version of 'react-native-device-info' by Rebecc
Get device information using react-native
* HOMEPAGE:
* https://github.com/react-native-community/react-native-device-info#readme
* https://github.com/rebeccahughes/react-native-device-info#readme
* LICENSE: MIT
@@ -1477,7 +1293,7 @@ SOFTWARE.
This product contains 'react-native-doc-viewer' by Philipp Hecht.
React Native Native Module Bridge Quicklock Document Viewer for IOS + Android supports pdf, png, jpg, xls, ppt, doc, docx, pptx, xlx + Video Player mp4 supported
React Native Native Module Bridge Quicklock Document Viewer for IOS + Android supports pdf, png, jpg, xls, ppt, doc, docx, pptx, xlx + Video Player mp4 supported
* HOMEPAGE:
* https://github.com/philipphecht/react-native-doc-viewer/blob/master/README.md
@@ -1543,6 +1359,31 @@ SOFTWARE.
---
## react-native-drawer-layout
This product contains 'react-native-drawer-layout' by Brent Vatne.
A platform-agnostic drawer layout. Pure JavaScript implementation on iOS and native implementation on Android. Why? Because the drawer layout is a useful component regardless of the platform! And if you can use it without changing any code, that's perfect
* HOMEPAGE:
* https://github.com/react-native-community/react-native-drawer-layout#readme
* LICENSE: MIT
Note: An original license file for this dependency is not available. We determined the type of license based on the package registry entry for this project. The following text has been prepared using a template from the SPDX Workgroup (https://spdx.org) for this type of license.
MIT License
Copyright (c) 2018 Brent Vatne
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' by master-atul.
@@ -1578,76 +1419,6 @@ SOFTWARE.
---
## react-native-gesture-handler
This product contains 'react-native-gesture-handler' by Krzysztof Magiera.
Experimental implementation of a new declarative API for gesture handling in react-native
* HOMEPAGE:
* https://github.com/kmagiera/react-native-gesture-handler#readme
* LICENSE: MIT
The MIT License (MIT)
Copyright (c) 2016 Krzysztof Magiera
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-haptic-feedback
This product contains 'react-native-haptic-feedback' by Milk and Cookies.
React-Native Haptic Feedback for iOS with a similar behaviour for Android
* HOMEPAGE:
* https://github.com/milk-and-cookies-io/react-native-haptic-feedback
* LICENSE: MIT
MIT License
Copyright (c) 2018 Michael Kuczera
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
This product contains a modified version of 'react-native-image-gallery' by Archriss.
@@ -1679,7 +1450,7 @@ This product contains 'react-native-image-picker' by Marc Shilling.
A React Native module that allows you to use native UI to select media from the device library or directly from the camera
* HOMEPAGE:
* https://github.com/react-community/react-native-image-picker
* https://github.com/marcshilling/react-native-image-picker#readme
* LICENSE: MIT
@@ -1742,41 +1513,6 @@ SOFTWARE.
---
## react-native-keyboard-tracking-view
This product contains a modified version of 'react-native-keyboard-tracking-view' by Artal Druk.
React Native UI component which tracks the keyboard
* HOMEPAGE:
* https://github.com/wix/react-native-keyboard-tracking-view
* LICENSE: MIT
The MIT License (MIT)
Copyright (c) 2016 Wix.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-keychain
This product contains 'react-native-keychain' by Joel Arvidsson.
@@ -1816,10 +1552,10 @@ SOFTWARE.
This product contains 'react-native-linear-gradient' by Brent Vatne.
A <LinearGradient> element for React Native
A <LinearGradient> element for react-native
* HOMEPAGE:
* https://github.com/react-native-community/react-native-linear-gradient#readme
* https://github.com/brentvatne/react-native-linear-gradient#readme
* LICENSE: MIT
@@ -1868,7 +1604,7 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH RE
## react-native-navigation
This product contains a modified version of 'react-native-navigation' by Wix.com.
This product contains 'react-native-navigation' by Tal Kol.
React Native Navigation - truly native navigation for iOS and Android
@@ -1877,27 +1613,26 @@ React Native Navigation - truly native navigation for iOS and Android
* LICENSE: MIT
MIT License
The MIT License (MIT)
Copyright (c) 2016 Wix.com
Copyright (c) Wix.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:
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.
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.
---
@@ -1939,7 +1674,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This product contains 'react-native-passcode-status' by Mark Vayngrib.
check if device-level passcode is supported/enabled/disabled
check passcode status on device
* HOMEPAGE:
* https://github.com/tradle/react-native-passcode-status
@@ -1974,7 +1709,7 @@ SOFTWARE.
This product contains 'react-native-permissions' by Yonah Forst.
Check and request user permissions in React Native.
Check user permissions in React Native
* HOMEPAGE:
* https://github.com/yonahforst/react-native-permissions
@@ -2053,16 +1788,16 @@ This package simplifies constructing the getItemLayout prop for react native Sec
Copyright (c) 2017 Jan Soendermann
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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
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.
---
@@ -2152,7 +1887,7 @@ Note: An original license file for this dependency is not available. We determin
MIT License
Copyright (c) 2019 Brent Vatne
Copyright (c) 2018 Brent Vatne
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:
@@ -2169,7 +1904,7 @@ This product contains 'react-native-svg' by React Native Community.
SVG library for react-native
* HOMEPAGE:
* https://github.com/react-native-community/react-native-svg#readme
* https://github.com/magicismight/react-native-svg#readme
* LICENSE: MIT
@@ -2197,6 +1932,78 @@ SOFTWARE.
---
## react-native-tableview
This product contains 'react-native-tableview' by Pavlo Aksonov.
Native iOS TableView wrapper for React Native
* HOMEPAGE:
* https://github.com/aksonov/react-native-tableview#readme
* LICENSE: BSD-2-Clause
Copyright (c) 2015, aksonov
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---
## react-native-tooltip
This product contains 'react-native-tooltip' by Chirag Jain.
A react-native wrapper for showing tooltips
* HOMEPAGE:
* https://github.com/chirag04/react-native-tooltip#readme
* LICENSE: MIT
The MIT License (MIT)
Copyright (c) 2015-2016 Chirag Jain
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
## react-native-vector-icons
This product contains 'react-native-vector-icons' by Joel Arvidsson.
@@ -2239,7 +2046,7 @@ This product contains 'react-native-video' by Brent Vatne.
A <Video /> element for react-native
* HOMEPAGE:
* https://github.com/react-native-community/react-native-video#readme
* https://github.com/brentvatne/react-native-video#readme
* LICENSE: MIT
@@ -2267,41 +2074,6 @@ SOFTWARE.
---
## react-native-webview
This product contains a modified version of 'react-native-webview' by Jamon Holmgren.
React Native WebView component for iOS, Android, and Windows 10 (coming soon)
* HOMEPAGE:
* https://github.com/react-native-community/react-native-webview#readme
* LICENSE: MIT
MIT License
Copyright (c) 2015-present, Facebook, 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.
---
## react-native-youtube
This product contains a modified version of 'react-native-youtube' by Param Aggarwal.
@@ -2384,7 +2156,7 @@ This product contains 'react-redux' by Dan Abramov.
Official React bindings for Redux
* HOMEPAGE:
* https://github.com/reduxjs/react-redux
* https://github.com/gaearon/react-redux
* LICENSE: MIT
@@ -2589,12 +2361,12 @@ SOFTWARE.
## reselect
This product contains 'reselect' by Lee Bannard.
This product contains 'reselect' by Redux.
Selectors for Redux.
* HOMEPAGE:
* https://github.com/reduxjs/reselect#readme
* https://github.com/reactjs/reselect#readme
* LICENSE: MIT
@@ -2668,7 +2440,7 @@ Display some placeholder stuff before rendering your text or media content in Re
* LICENSE: MIT
Copyright (c) 2004-Today Marvin Frachet
Copyright (c) 2004-2018 Marvin Frachet
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -2770,7 +2542,7 @@ Note: An original license file for this dependency is not available. We determin
MIT License
Copyright (c) 2019 Brian Grinstead
Copyright (c) 2018 Brian Grinstead
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:

View File

@@ -1,4 +1,4 @@
Please make sure you've read the [pull request](https://developers.mattermost.com/contribute/getting-started/contribution-checklist/) section of our [code contribution guidelines](https://developers.mattermost.com/contribute/getting-started/).
Please make sure you've read the [pull request](http://docs.mattermost.com/developer/contribution-guide.html#preparing-a-pull-request) section of our [code contribution guidelines](http://docs.mattermost.com/developer/contribution-guide.html).
When filling in a section please remove the help text and the above text.

View File

@@ -1,16 +1,16 @@
# Mattermost Mobile
- **Supported Server versions:** 4.10+
- **Supported iOS versions:** 10.3+
- **Supported Android versions:** 7.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).
You can download our apps from the [App Store](https://about.mattermost.com/mattermost-ios-app/) or [Google Play Store](https://about.mattermost.com/mattermost-android-app/), or [build them yourself](https://developers.mattermost.com/contribute/mobile/build-your-own/).
You can download our apps from the [App Store](https://about.mattermost.com/mattermost-ios-app/) or [Google Play Store](https://about.mattermost.com/mattermost-android-app/), or [build them yourself](https://about.mattermost.com/default-enterprise-app-store).
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 deploy your own [Mattermost Push Notification Service](https://github.com/mattermost/mattermost-push-proxy/releases).
**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
@@ -19,26 +19,22 @@ We plan on releasing monthly updates with new features - check the [changelog](h
To help with testing app updates before they're released, you can:
1. Sign up to be a beta tester
- [Android](https://play.google.com/apps/testing/com.mattermost.rnbeta)
- [iOS](https://testflight.apple.com/join/Q7Rx7K9P)
2. Install the `Mattermost Beta` app. New updates in the Beta app are released periodically. You will receive a notification when the new updates are available.
- [Android](https://play.google.com/apps/testing/com.mattermost.rnbeta)
- [iOS](https://mattermost-fastlane.herokuapp.com/)
2. Install the `Mattermost Beta` app. New updates in the Beta app are released each Monday. You will receive a notification when the new updates are available.
3. File any bugs you find by filing a [GitHub issue](https://github.com/mattermost/mattermost-mobile/issues) with:
- Device information
- Repro steps
- Observed behavior (including screenshot / video when possible)
- Expected behavior
- Device information
- Repro steps
- Observed behavior (including screenshot / video when possible)
- Expected behavior
4. (Optional) [Sign up for our team site](https://pre-release.mattermost.com/signup_user_complete/?id=f1924a8db44ff3bb41c96424cdc20676)
- Join the [Native Mobile Apps channel](https://pre-release.mattermost.com/core/channels/native-mobile-apps) to see what's new and discuss feedback with other contributors and the core team
You can leave the Beta testing program at any time:
- On Android, [click this link](https://play.google.com/apps/testing/com.mattermost.rnbeta) while logged in with your Google Play email address used to opt-in for the Beta program, then click **Leave the program**.
- On iOS, access the `Mattermost Beta` app page in TestFlight and click **Stop Testing**.
- Join the [Native Mobile Apps channel](https://pre-release.mattermost.com/core/channels/native-mobile-apps) to see what's new and discuss feedback with other contributors and the core team
### Contribute Code
1. Look in [GitHub issues](https://mattermost.com/pl/help-wanted-mattermost-mobile) for issues marked as [Help Wanted]
1. Look in [GitHub issues](https://github.com/mattermost/mattermost-mobile/issues) for issues marked as [Help Wanted]
2. Comment to let people know youre working on it
3. Follow [these instructions](https://developers.mattermost.com/contribute/mobile/developer-setup/) to set up your developer environment
3. Follow [these instructions](https://docs.mattermost.com/developer/mobile-developer-setup.html) to set up your developer environment
4. Join the [Native Mobile Apps channel](https://pre-release.mattermost.com/core/channels/native-mobile-apps) on our team site to ask questions

View File

@@ -75,7 +75,7 @@ import com.android.build.OutputFile
project.ext.react = [
entryFile: "index.js",
bundleCommand: "ram-bundle",
bundleConfig: "metro.config.js"
bundleConfig: "packager-config.js"
]
apply from: "../../node_modules/react-native/react.gradle"
@@ -107,27 +107,17 @@ def enableProguardInReleaseBuilds = false
android {
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
pickFirst '**/libjsc.so'
pickFirst '**/libc++_shared.so'
}
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.mattermost.rnbeta"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5"
versionCode 239
versionName "1.24.0"
versionCode 151
versionName "1.13.0"
multiDexEnabled = true
ndk {
abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
abiFilters "armeabi-v7a", "x86"
}
}
@@ -146,7 +136,7 @@ android {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
include "armeabi-v7a", "x86"
}
}
buildTypes {
@@ -170,7 +160,7 @@ android {
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
def versionCodes = ["armeabi-v7a":1, "x86":2, "arm64-v8a": 3, "x86_64": 4]
def versionCodes = ["armeabi-v7a":1, "x86":2]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
@@ -178,11 +168,6 @@ android {
}
}
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
repositories {
@@ -194,31 +179,18 @@ repositories {
configurations.all {
resolutionStrategy {
eachDependency { DependencyResolveDetails details ->
if (details.requested.name == 'play-services-base') {
details.useTarget group: details.requested.group, name: details.requested.name, version: '15.0.1'
}
if (details.requested.name == 'play-services-tasks') {
details.useTarget group: details.requested.group, name: details.requested.name, version: '15.0.1'
}
if (details.requested.name == 'play-services-stats') {
details.useTarget group: details.requested.group, name: details.requested.name, version: '15.0.1'
}
if (details.requested.name == 'play-services-basement') {
details.useTarget group: details.requested.group, name: details.requested.name, version: '15.0.1'
if (details.requested.name == 'android-jsc') {
details.useTarget group: details.requested.group, name: 'android-jsc-intl', version: 'r224109'
}
}
}
}
dependencies {
// Make sure to put android-jsc at the top
implementation "org.webkit:android-jsc-intl:r241213"
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:percent:28.0.0'
implementation "com.google.firebase:firebase-messaging:17.3.0"
implementation 'com.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')
@@ -226,6 +198,7 @@ dependencies {
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')
@@ -238,12 +211,7 @@ dependencies {
implementation project(':react-native-sentry')
implementation project(':react-native-exception-handler')
implementation project(':rn-fetch-blob')
implementation project(':react-native-webview')
implementation project(':react-native-gesture-handler')
implementation project(':@react-native-community_async-storage')
implementation project(':@react-native-community_netinfo')
implementation project(':react-native-android-open-settings')
implementation project(':react-native-haptic-feedback')
implementation project(':react-native-recyclerview-list')
// For animated GIF support
implementation 'com.facebook.fresco:fresco:1.10.0'
@@ -259,5 +227,3 @@ task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
apply plugin: 'com.google.gms.google-services'

View File

@@ -26,7 +26,7 @@
],
"services": {
"analytics_service": {
"status": 2
"status": 1
},
"appinvite_service": {
"status": 1,
@@ -57,7 +57,7 @@
],
"services": {
"analytics_service": {
"status": 2
"status": 1
},
"appinvite_service": {
"status": 1,
@@ -88,7 +88,7 @@
],
"services": {
"analytics_service": {
"status": 2
"status": 1
},
"appinvite_service": {
"status": 1,
@@ -101,4 +101,4 @@
}
],
"configuration_version": "1"
}
}

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
</manifest>

View File

@@ -2,8 +2,8 @@
package="com.mattermost.rnbeta">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission-sdk-23 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"/>
@@ -12,15 +12,11 @@
<application
android:name=".MainApplication"
android:allowBackup="false"
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme"
android:installLocation="auto"
android:networkSecurityConfig="@xml/network_security_config"
>
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
<meta-data android:name="android.content.APP_RESTRICTIONS"
android:resource="@xml/app_restrictions" />
@@ -40,13 +36,12 @@
<service android:name=".NotificationDismissService"
android:enabled="true"
android:exported="false" />
<receiver android:name=".NotificationReplyBroadcastReceiver"
<service android:name=".NotificationReplyService"
android:enabled="true"
android:exported="false" />
<activity
android:name="com.reactnativenavigation.controllers.NavigationActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:resizeableActivity="true"/>
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"/>
<activity
android:name="com.mattermost.share.ShareActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
@@ -57,7 +52,7 @@
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<!-- for sharing-->
// for sharing
<data android:mimeType="*/*" />
</intent-filter>
</activity>

View File

@@ -1,7 +1,6 @@
package com.mattermost.react_native_interface;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
/**
* ResolvePromise: Helper class that abstracts boilerplate
@@ -17,41 +16,16 @@ public class ResolvePromise implements Promise {
}
@Override
public void reject(String code, WritableMap map) {
}
@Override
public void reject(String code, Throwable e) {
}
@Override
public void reject(Throwable e, WritableMap map) {
}
@Override
public void reject(String code, Throwable e, WritableMap map) {
}
@Override
public void reject(String code, String message, Throwable e, WritableMap map) {
}
@Override
public void reject(String code, String message, Throwable e) {
}
@Override
public void reject(String code, String message, WritableMap map) {
}
@Override
public void reject(String message) {

View File

@@ -1,16 +1,11 @@
package com.mattermost.rnbeta;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
import android.app.Person.Builder;
import android.app.RemoteInput;
import android.app.NotificationChannel;
import android.content.Intent;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
@@ -19,14 +14,15 @@ import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Build;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.RemoteInput;
import android.provider.Settings.System;
import android.util.Log;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.lang.reflect.Field;
import com.wix.reactnativenotifications.core.notification.PushNotification;
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
@@ -35,9 +31,12 @@ import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import com.wix.reactnativenotifications.core.JsIOHelper;
import com.wix.reactnativenotifications.helpers.ApplicationBadgeHelper;
import android.util.Log;
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
public class CustomPushNotification extends PushNotification {
public static final int MESSAGE_NOTIFICATION_ID = 435345;
public static final String GROUP_KEY_MESSAGES = "mm_group_key_messages";
public static final String NOTIFICATION_ID = "notificationId";
@@ -48,46 +47,31 @@ public class CustomPushNotification extends PushNotification {
private static LinkedHashMap<String,List<Bundle>> channelIdToNotification = new LinkedHashMap<String,List<Bundle>>();
private static AppLifecycleFacade lifecycleFacade;
private static Context context;
private static int badgeCount = 0;
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
this.context = context;
}
public static void clearNotification(Context mContext, int notificationId, String channelId) {
public static void clearNotification(int notificationId, String channelId) {
if (notificationId != -1) {
Object objCount = channelIdToNotificationCount.get(channelId);
Integer count = -1;
if (objCount != null) {
count = (Integer)objCount;
}
channelIdToNotificationCount.remove(channelId);
channelIdToNotification.remove(channelId);
if (mContext != null) {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (context != null) {
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
if (count != -1) {
int total = CustomPushNotification.badgeCount - count;
int badgeCount = total < 0 ? 0 : total;
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), badgeCount);
CustomPushNotification.badgeCount = badgeCount;
}
}
}
}
public static void clearAllNotifications(Context mContext) {
channelIdToNotificationCount.clear();
channelIdToNotification.clear();
if (mContext != null) {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), 0);
public static void clearNotification(Context mContext, int notificationId, String channelId) {
if (notificationId != -1) {
channelIdToNotificationCount.remove(channelId);
channelIdToNotification.remove(channelId);
if (mContext != null) {
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
}
}
}
@@ -96,13 +80,7 @@ public class CustomPushNotification extends PushNotification {
Bundle data = mNotificationProps.asBundle();
final String channelId = data.getString("channel_id");
final String type = data.getString("type");
final String ackId = data.getString("ack_id");
int notificationId = MESSAGE_NOTIFICATION_ID;
if (ackId != null) {
notificationReceiptDelivery(ackId, type);
}
if (channelId != null) {
notificationId = channelId.hashCode();
Object objCount = channelIdToNotificationCount.get(channelId);
@@ -120,12 +98,6 @@ public class CustomPushNotification extends PushNotification {
list = Collections.synchronizedList((List)bundleArray);
}
synchronized (list) {
if (!"clear".equals(type)) {
String senderName = getSenderName(data.getString("sender_name"), data.getString("channel_name"), data.getString("message"));
data.putLong("time", new Date().getTime());
data.putString("sender_name", senderName);
data.putString("sender_id", data.getString("sender_id"));
}
list.add(0, data);
channelIdToNotification.put(channelId, list);
}
@@ -147,6 +119,7 @@ public class CustomPushNotification extends PushNotification {
channelIdToNotificationCount.remove(channelId);
channelIdToNotification.remove(channelId);
digestNotification();
clearAllNotifications();
}
@Override
@@ -168,40 +141,25 @@ 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 Android Oreo or above we need to register a channel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String CHANNEL_ID = "channel_01";
String CHANNEL_NAME = "Mattermost notifications";
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_HIGH);
channel.setShowBadge(true);
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
notification.setChannelId(CHANNEL_ID);
}
Bundle bundle = mNotificationProps.asBundle();
String version = bundle.getString("version");
String channelId = bundle.getString("channel_id");
String channelName = bundle.getString("channel_name");
String senderName = bundle.getString("sender_name");
String senderId = bundle.getString("sender_id");
String postId = bundle.getString("post_id");
String badge = bundle.getString("badge");
String smallIcon = bundle.getString("smallIcon");
String largeIcon = bundle.getString("largeIcon");
int notificationId = channelId != null ? channelId.hashCode() : MESSAGE_NOTIFICATION_ID;
String title = null;
if (version != null && version.equals("v2")) {
title = channelName;
title = bundle.getString("channel_name");
} else {
title = bundle.getString("title");
}
@@ -211,12 +169,19 @@ public class CustomPushNotification extends PushNotification {
title = mContext.getPackageManager().getApplicationLabel(appInfo).toString();
}
String channelId = bundle.getString("channel_id");
String postId = bundle.getString("post_id");
int notificationId = channelId != null ? channelId.hashCode() : MESSAGE_NOTIFICATION_ID;
String message = bundle.getString("message");
String subText = bundle.getString("subText");
String numberString = bundle.getString("badge");
String smallIcon = bundle.getString("smallIcon");
String largeIcon = bundle.getString("largeIcon");
Bundle b = bundle.getBundle("userInfo");
if (b == null) {
b = new Bundle();
if (b != null) {
notification.addExtras(b);
}
b.putString("channel_id", channelId);
notification.addExtras(b);
int smallIconResId;
int largeIconResId;
@@ -241,80 +206,66 @@ public class CustomPushNotification extends PushNotification {
largeIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
}
if (badge != null) {
int badgeCount = Integer.parseInt(badge);
CustomPushNotification.badgeCount = badgeCount;
notification.setNumber(badgeCount);
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), CustomPushNotification.badgeCount);
if (numberString != null) {
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), Integer.parseInt(numberString));
}
if (android.text.TextUtils.isEmpty(senderName)) {
senderName = getSenderName(senderName, channelName, bundle.getString("message"));
}
String personId = senderId;
if (!android.text.TextUtils.isEmpty(channelName)) {
personId = channelId;
}
Notification.MessagingStyle messagingStyle;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
messagingStyle = new Notification.MessagingStyle("");
} else {
Person sender = new Person.Builder()
.setKey(senderId)
.setName("")
.build();
messagingStyle = new Notification.MessagingStyle(sender);
}
if (title != null && (!title.startsWith("@") || channelName != senderName)) {
messagingStyle
.setConversationTitle(title);
}
List<Bundle> bundleArray = channelIdToNotification.get(channelId);
List<Bundle> list;
if (bundleArray != null) {
list = new ArrayList<Bundle>(bundleArray);
} else {
list = new ArrayList<Bundle>();
list.add(bundle);
}
int listCount = list.size() - 1;
for (int i = listCount; i >= 0; i--) {
Bundle data = list.get(i);
String message = data.getString("message");
String previousPersonName = getSenderName(data.getString("sender_name"), channelName, message);
String previousPersonId = data.getString("sender_id");
if (title == null || !android.text.TextUtils.isEmpty(previousPersonName)) {
message = removeSenderFromMessage(previousPersonName, channelName, message);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
messagingStyle.addMessage(message, data.getLong("time"), previousPersonName);
} else {
Person sender = new Person.Builder()
.setKey(previousPersonId)
.setName(previousPersonName)
.build();
messagingStyle.addMessage(message, data.getLong("time"), sender);
}
}
int numMessages = getMessageCountInChannel(channelId);
notification
.setContentIntent(intent)
.setGroupSummary(true)
.setStyle(messagingStyle)
.setSmallIcon(smallIconResId)
.setVisibility(Notification.VISIBILITY_PRIVATE)
.setPriority(Notification.PRIORITY_HIGH)
.setAutoCancel(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setBadgeIconType(Notification.BADGE_ICON_SMALL);
if (numMessages == 1) {
notification
.setContentTitle(title)
.setContentText(message)
.setStyle(new Notification.BigTextStyle()
.bigText(message));
} else {
String summaryTitle = null;
if (version != null && version.equals("v2")) {
summaryTitle = String.format("(%d) %s", numMessages, title);
} else {
summaryTitle = String.format("%s (%d)", title, numMessages);
}
Notification.InboxStyle style = new Notification.InboxStyle();
List<Bundle> bundleArray = channelIdToNotification.get(channelId);
List<Bundle> list;
if (bundleArray != null) {
list = new ArrayList<Bundle>(bundleArray);
} else {
list = new ArrayList<Bundle>();
}
if (version != null && version.equals("v2")) {
style.addLine(message);
}
for (Bundle data : list) {
String msg = data.getString("message");
if (msg != message) {
style.addLine(data.getString("message"));
}
}
if (version != null && version.equals("v2")) {
notification
.setContentTitle(summaryTitle)
.setContentText(message)
.setStyle(style);
} else {
style.setBigContentTitle(message)
.setSummaryText(String.format("+%d more", (numMessages - 1)));
notification.setStyle(style)
.setContentTitle(summaryTitle);
}
}
// Let's add a delete intent when the notification is dismissed
@@ -328,11 +279,17 @@ public class CustomPushNotification extends PushNotification {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && postId != null) {
Intent replyIntent = new Intent(mContext, NotificationReplyBroadcastReceiver.class);
Intent replyIntent = new Intent(mContext, NotificationReplyService.class);
replyIntent.setAction(KEY_TEXT_REPLY);
replyIntent.putExtra(NOTIFICATION_ID, notificationId);
replyIntent.putExtra("pushNotification", bundle);
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
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);
}
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
.setLabel("Reply")
@@ -354,6 +311,10 @@ public class CustomPushNotification extends PushNotification {
notification.setLargeIcon(largeIconBitmap);
}
if (subText != null) {
notification.setSubText(subText);
}
String soundUri = notificationPreferences.getNotificationSound();
if (soundUri != null) {
if (soundUri != "none") {
@@ -393,35 +354,15 @@ public class CustomPushNotification extends PushNotification {
private void cancelNotification(Bundle data, int notificationId) {
final String channelId = data.getString("channel_id");
final String numberString = data.getString("badge");
CustomPushNotification.badgeCount = Integer.parseInt(numberString);
CustomPushNotification.clearNotification(mContext.getApplicationContext(), notificationId, channelId);
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), CustomPushNotification.badgeCount);
}
private String getSenderName(String senderName, String channelName, String message) {
if (senderName != null) {
return senderName;
} else if (channelName != null && channelName.startsWith("@")) {
return channelName;
String numberString = data.getString("badge");
if (numberString != null) {
ApplicationBadgeHelper.instance.setApplicationIconBadgeNumber(mContext.getApplicationContext(), Integer.parseInt(numberString));
}
String name = message.split(":")[0];
if (name != message) {
return name;
}
return " ";
}
private String removeSenderFromMessage(String senderName, String channelName, String message) {
String sender = String.format("%s", getSenderName(senderName, channelName, message));
return message.replaceFirst(sender, "").replaceFirst(": ", "").trim();
}
private void notificationReceiptDelivery(String ackId, String type) {
ReceiptDelivery.send(context, ackId, type);
channelIdToNotificationCount.remove(channelId);
channelIdToNotification.remove(channelId);
final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
}
}

View File

@@ -1,39 +0,0 @@
package com.mattermost.rnbeta;
import android.content.Context;
import com.wix.reactnativenotifications.core.AppLaunchHelper;
import com.wix.reactnativenotifications.core.notificationdrawer.PushNotificationsDrawer;
import com.wix.reactnativenotifications.core.notificationdrawer.IPushNotificationsDrawer;
import com.wix.reactnativenotifications.core.notificationdrawer.INotificationsDrawerApplication;
import com.wix.reactnativenotifications.helpers.PushNotificationHelper;
import static com.wix.reactnativenotifications.Defs.LOGTAG;
public class CustomPushNotificationDrawer extends PushNotificationsDrawer {
final protected Context mContext;
final protected AppLaunchHelper mAppLaunchHelper;
protected CustomPushNotificationDrawer(Context context, AppLaunchHelper appLaunchHelper) {
super(context, appLaunchHelper);
mContext = context;
mAppLaunchHelper = appLaunchHelper;
}
@Override
public void onAppInit() {
}
@Override
public void onAppVisible() {
}
@Override
public void onNotificationOpened() {
}
@Override
public void onCancelAllLocalNotifications() {
CustomPushNotification.clearAllNotifications(mContext);
cancelAllScheduledNotifications();
}
}

View File

@@ -0,0 +1,146 @@
package com.mattermost.rnbeta;
import android.app.Application;
import android.support.annotation.Nullable;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.mattermost.react_native_interface.AsyncStorageHelper;
import com.mattermost.react_native_interface.KeysReadableArray;
import com.mattermost.react_native_interface.ResolvePromise;
import com.oblador.keychain.KeychainModule;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class InitializationModule extends ReactContextBaseJavaModule {
static final String TOOLBAR_BACKGROUND = "TOOLBAR_BACKGROUND";
static final String TOOLBAR_TEXT_COLOR = "TOOLBAR_TEXT_COLOR";
static final String APP_BACKGROUND = "APP_BACKGROUND";
private final Application mApplication;
public InitializationModule(Application application, ReactApplicationContext reactContext) {
super(reactContext);
mApplication = application;
}
@Override
public String getName() {
return "Initialization";
}
@Nullable
@Override
public Map<String, Object> getConstants() {
Map<String, Object> constants = new HashMap<>();
/**
* Package all native module variables in constants
* in order to avoid the native bridge
*
* KeyStore:
* credentialsExist
* deviceToken
* currentUserId
* token
* url
*
* AsyncStorage:
* toolbarBackground
* toolbarTextColor
* appBackground
*
* Miscellaneous:
* MattermostManaged.Config
* replyFromPushNotification
*/
MainApplication app = (MainApplication) mApplication;
final Boolean[] credentialsExist = {false};
final WritableMap[] credentials = {null};
final Object[] config = {null};
// Get KeyStore credentials
KeychainModule module = new KeychainModule(this.getReactApplicationContext());
module.getGenericPasswordForOptions(null, new ResolvePromise() {
@Override
public void resolve(@Nullable Object value) {
if (value instanceof Boolean && !(Boolean)value) {
credentialsExist[0] = false;
return;
}
WritableMap map = (WritableMap) value;
if (map != null) {
credentialsExist[0] = true;
credentials[0] = map;
}
}
});
// Get managedConfig from MattermostManagedModule
MattermostManagedModule.getInstance().getConfig(new ResolvePromise() {
@Override
public void resolve(@Nullable Object value) {
WritableNativeMap nativeMap = (WritableNativeMap) value;
config[0] = value;
}
});
// Get AsyncStorage key/values
final ArrayList<String> keys = new ArrayList<String>(5);
keys.add(TOOLBAR_BACKGROUND);
keys.add(TOOLBAR_TEXT_COLOR);
keys.add(APP_BACKGROUND);
KeysReadableArray asyncStorageKeys = new KeysReadableArray() {
@Override
public int size() {
return keys.size();
}
@Override
public String getString(int index) {
return keys.get(index);
}
};
AsyncStorageHelper asyncStorage = new AsyncStorageHelper(this.getReactApplicationContext());
HashMap<String, String> asyncStorageResults = asyncStorage.multiGet(asyncStorageKeys);
String toolbarBackground = asyncStorageResults.get(TOOLBAR_BACKGROUND);
String toolbarTextColor = asyncStorageResults.get(TOOLBAR_TEXT_COLOR);
String appBackground = asyncStorageResults.get(APP_BACKGROUND);
if (toolbarBackground != null
&& toolbarTextColor != null
&& appBackground != null) {
constants.put("themesExist", true);
constants.put("toolbarBackground", toolbarBackground);
constants.put("toolbarTextColor", toolbarTextColor);
constants.put("appBackground", appBackground);
} else {
constants.put("themesExist", false);
}
if (credentialsExist[0]) {
constants.put("credentialsExist", true);
constants.put("credentials", credentials[0]);
} else {
constants.put("credentialsExist", false);
}
constants.put("managedConfig", config[0]);
constants.put("replyFromPushNotification", app.replyFromPushNotification);
app.replyFromPushNotification = false;
return constants;
}
}

View File

@@ -0,0 +1,36 @@
package com.mattermost.rnbeta;
import android.app.Application;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class InitializationPackage implements ReactPackage {
private final Application mApplication;
public InitializationPackage(Application application) {
mApplication = application;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new InitializationModule(mApplication, reactContext));
}
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@@ -2,14 +2,12 @@ package com.mattermost.rnbeta;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.reactnativenavigation.controllers.SplashActivity;
import com.reactnativenavigation.NavigationActivity;
public class MainActivity extends NavigationActivity {
public class MainActivity extends SplashActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.launch_screen);
/**
* Reference: https://stackoverflow.com/questions/7944338/resume-last-activity-when-launcher-icon-is-clicked
@@ -25,4 +23,9 @@ public class MainActivity extends NavigationActivity {
return;
}
}
@Override
public int getSplashLayout() {
return R.layout.launch_screen;
}
}

View File

@@ -1,98 +1,49 @@
package com.mattermost.rnbeta;
import com.mattermost.share.SharePackage;
import android.app.Activity;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.content.Context;
import android.content.RestrictionsManager;
import android.os.Bundle;
import android.util.Log;
import java.io.File;
import java.util.HashMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.mattermost.share.ShareModule;
import com.learnium.RNDeviceInfo.RNDeviceModule;
import com.imagepicker.ImagePickerModule;
import com.psykar.cookiemanager.CookieManagerModule;
import com.oblador.vectoricons.VectorIconsModule;
import com.wix.reactnativenotifications.RNNotificationsModule;
import io.tradle.react.LocalAuthModule;
import com.gantix.JailMonkey.JailMonkeyModule;
import com.RNFetchBlob.RNFetchBlob;
import io.sentry.RNSentryModule;
import io.sentry.RNSentryEventEmitter;
import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerModule;
import com.inprogress.reactnativeyoutube.YouTubeStandaloneModule;
import com.reactlibrary.RNReactNativeDocViewerModule;
import com.reactnativedocumentpicker.DocumentPicker;
import com.oblador.keychain.KeychainModule;
import com.reactnativecommunity.asyncstorage.AsyncStorageModule;
import com.reactnativecommunity.netinfo.NetInfoModule;
import com.levelasquez.androidopensettings.AndroidOpenSettings;
import com.mkuczera.RNReactNativeHapticFeedbackModule;
import com.reactnativecommunity.webview.RNCWebViewPackage;
import com.reactnativedocumentpicker.ReactNativeDocumentPicker;
import com.oblador.keychain.KeychainPackage;
import com.reactlibrary.RNReactNativeDocViewerPackage;
import com.brentvatne.react.ReactVideoPackage;
import com.BV.LinearGradient.LinearGradientPackage;
import com.horcrux.svg.SvgPackage;
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import com.inprogress.reactnativeyoutube.ReactNativeYouTube;
import io.sentry.RNSentryPackage;
import com.masteratul.exceptionhandler.ReactNativeExceptionHandlerPackage;
import com.RNFetchBlob.RNFetchBlobPackage;
import com.gantix.JailMonkey.JailMonkeyPackage;
import io.tradle.react.LocalAuthPackage;
import com.github.godness84.RNRecyclerViewList.RNRecyclerviewListPackage;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import com.imagepicker.ImagePickerPackage;
import com.gnet.bottomsheet.RNBottomSheetPackage;
import com.learnium.RNDeviceInfo.RNDeviceInfo;
import com.psykar.cookiemanager.CookieManagerPackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.BV.LinearGradient.LinearGradientPackage;
import com.reactnativenavigation.NavigationApplication;
import com.reactnativenavigation.react.NavigationReactNativeHost;
import com.reactnativenavigation.react.ReactGateway;
import com.wix.reactnativenotifications.RNNotificationsPackage;
import com.wix.reactnativenotifications.core.notification.INotificationsApplication;
import com.wix.reactnativenotifications.core.notification.IPushNotification;
import com.wix.reactnativenotifications.core.notificationdrawer.IPushNotificationsDrawer;
import com.wix.reactnativenotifications.core.notificationdrawer.INotificationsDrawerApplication;
import com.wix.reactnativenotifications.core.AppLaunchHelper;
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import com.wix.reactnativenotifications.core.JsIOHelper;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.TurboReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.soloader.SoLoader;
import com.mattermost.share.RealPathUtil;
public class MainApplication extends NavigationApplication implements INotificationsApplication, INotificationsDrawerApplication {
public static MainApplication instance;
import java.util.Arrays;
import java.util.List;
public class MainApplication extends NavigationApplication implements INotificationsApplication {
public NotificationsLifecycleFacade notificationsLifecycleFacade;
public Boolean sharedExtensionIsOpened = false;
public long APP_START_TIME;
public long RELOAD;
public long CONTENT_APPEARED;
public long PROCESS_PACKAGES_START;
public long PROCESS_PACKAGES_END;
private Bundle mManagedConfig = null;
@Override
protected ReactGateway createReactGateway() {
ReactNativeHost host = new NavigationReactNativeHost(this, isDebug(), createAdditionalReactPackages()) {
@Override
protected String getJSMainModuleName() {
return "index";
}
};
return new ReactGateway(this, isDebug(), host);
}
public Boolean replyFromPushNotification = false;
@Override
public boolean isDebug() {
@@ -105,121 +56,53 @@ public class MainApplication extends NavigationApplication implements INotificat
// Add the packages you require here.
// No need to add RnnPackage and MainReactPackage
return Arrays.<ReactPackage>asList(
new TurboReactPackage() {
@Override
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
switch (name) {
case "MattermostShare":
return new ShareModule(instance, reactContext);
case "RNDeviceInfo":
return new RNDeviceModule(reactContext, false);
case "ImagePickerManager":
return new ImagePickerModule(reactContext, R.style.DefaultExplainingPermissionsTheme);
case "RNCookieManagerAndroid":
return new CookieManagerModule(reactContext);
case "RNVectorIconsModule":
return new VectorIconsModule(reactContext);
case "WixRNNotifications":
return new RNNotificationsModule(instance, reactContext);
case "RNLocalAuth":
return new LocalAuthModule(reactContext);
case "JailMonkey":
return new JailMonkeyModule(reactContext);
case "RNFetchBlob":
return new RNFetchBlob(reactContext);
case "MattermostManaged":
return MattermostManagedModule.getInstance(reactContext);
case "NotificationPreferences":
return NotificationPreferencesModule.getInstance(instance, reactContext);
case "RNTextInputReset":
return new RNTextInputResetModule(reactContext);
case "RNSentry":
return new RNSentryModule(reactContext);
case "RNSentryEventEmitter":
return new RNSentryEventEmitter(reactContext);
case "ReactNativeExceptionHandler":
return new ReactNativeExceptionHandlerModule(reactContext);
case "YouTubeStandaloneModule":
return new YouTubeStandaloneModule(reactContext);
case "RNReactNativeDocViewer":
return new RNReactNativeDocViewerModule(reactContext);
case "RNDocumentPicker":
return new DocumentPicker(reactContext);
case "RNKeychainManager":
return new KeychainModule(reactContext);
case AsyncStorageModule.NAME:
return new AsyncStorageModule(reactContext);
case NetInfoModule.NAME:
return new NetInfoModule(reactContext);
case "RNAndroidOpenSettings":
return new AndroidOpenSettings(reactContext);
case "RNReactNativeHapticFeedbackModule":
return new RNReactNativeHapticFeedbackModule(reactContext);
default:
throw new IllegalArgumentException("Could not find module " + name);
}
}
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return new ReactModuleInfoProvider() {
@Override
public Map<String, ReactModuleInfo> getReactModuleInfos() {
Map<String, ReactModuleInfo> map = new HashMap<>();
map.put("MattermostManaged", new ReactModuleInfo("MattermostManaged", "com.mattermost.rnbeta.MattermostManagedModule", false, false, false, false, false));
map.put("NotificationPreferences", new ReactModuleInfo("NotificationPreferences", "com.mattermost.rnbeta.NotificationPreferencesModule", false, false, false, false, false));
map.put("RNTextInputReset", new ReactModuleInfo("RNTextInputReset", "com.mattermost.rnbeta.RNTextInputResetModule", false, false, false, false, false));
map.put("MattermostShare", new ReactModuleInfo("MattermostShare", "com.mattermost.share.ShareModule", false, false, true, false, false));
map.put("RNDeviceInfo", new ReactModuleInfo("RNDeviceInfo", "com.learnium.RNDeviceInfo.RNDeviceModule", false, false, true, false, false));
map.put("ImagePickerManager", new ReactModuleInfo("ImagePickerManager", "com.imagepicker.ImagePickerModule", false, false, false, false, false));
map.put("RNCookieManagerAndroid", new ReactModuleInfo("RNCookieManagerAndroid", "com.psykar.cookiemanager.CookieManagerModule", false, false, false, false, false));
map.put("RNVectorIconsModule", new ReactModuleInfo("RNVectorIconsModule", "com.oblador.vectoricons.VectorIconsModule", false, false, false, false, false));
map.put("WixRNNotifications", new ReactModuleInfo("WixRNNotifications", "com.wix.reactnativenotifications.RNNotificationsModule", false, false, false, false, false));
map.put("RNLocalAuth", new ReactModuleInfo("RNLocalAuth", "io.tradle.react.LocalAuthModule", false, false, false, false, false));
map.put("JailMonkey", new ReactModuleInfo("JailMonkey", "com.gantix.JailMonkey.JailMonkeyModule", false, false, true, false, false));
map.put("RNFetchBlob", new ReactModuleInfo("RNFetchBlob", "com.RNFetchBlob.RNFetchBlob", false, false, true, false, false));
map.put("RNSentry", new ReactModuleInfo("RNSentry", "com.sentry.RNSentryModule", false, false, true, false, false));
map.put("RNSentryEventEmitter", new ReactModuleInfo("RNSentryEventEmitter", "com.sentry.RNSentryEventEmitter", false, false, true, false, false));
map.put("ReactNativeExceptionHandler", new ReactModuleInfo("ReactNativeExceptionHandler", "com.masteratul.exceptionhandler.ReactNativeExceptionHandlerModule", false, false, false, false, false));
map.put("YouTubeStandaloneModule", new ReactModuleInfo("YouTubeStandaloneModule", "com.inprogress.reactnativeyoutube.YouTubeStandaloneModule", false, false, false, false, false));
map.put("RNReactNativeDocViewer", new ReactModuleInfo("RNReactNativeDocViewer", "com.reactlibrary.RNReactNativeDocViewerModule", false, false, false, false, false));
map.put("RNDocumentPicker", new ReactModuleInfo("RNDocumentPicker", "com.reactnativedocumentpicker.DocumentPicker", false, false, false, false, false));
map.put("RNKeychainManager", new ReactModuleInfo("RNKeychainManager", "com.oblador.keychain.KeychainModule", false, false, true, false, false));
map.put(AsyncStorageModule.NAME, new ReactModuleInfo(AsyncStorageModule.NAME, "com.reactnativecommunity.asyncstorage.AsyncStorageModule", false, false, false, false, false));
map.put(NetInfoModule.NAME, new ReactModuleInfo(NetInfoModule.NAME, "com.reactnativecommunity.netinfo.NetInfoModule", false, false, false, false, false));
map.put("RNAndroidOpenSettings", new ReactModuleInfo("RNAndroidOpenSettings", "com.levelasquez.androidopensettings.AndroidOpenSettings", false, false, false, false, false));
map.put("RNReactNativeHapticFeedbackModule", new ReactModuleInfo("RNReactNativeHapticFeedback", "com.mkuczera.RNReactNativeHapticFeedbackModule", false, false, false, false, false));
return map;
}
};
}
},
new RNCWebViewPackage(),
new ImagePickerPackage(),
new RNBottomSheetPackage(),
new RNDeviceInfo(),
new CookieManagerPackage(),
new VectorIconsPackage(),
new SvgPackage(),
new LinearGradientPackage(),
new RNNotificationsPackage(this),
new LocalAuthPackage(),
new JailMonkeyPackage(),
new RNFetchBlobPackage(),
new MattermostPackage(this),
new RNSentryPackage(),
new ReactNativeExceptionHandlerPackage(),
new ReactNativeYouTube(),
new ReactVideoPackage(),
new RNGestureHandlerPackage(),
new RNPasteableTextInputPackage()
new RNReactNativeDocViewerPackage(),
new ReactNativeDocumentPicker(),
new SharePackage(this),
new KeychainPackage(),
new InitializationPackage(this),
new RNRecyclerviewListPackage()
);
}
@Override
public String getJSMainModuleName() {
return "index";
}
@Override
public void onCreate() {
super.onCreate();
instance = this;
registerActivityLifecycleCallbacks(new ManagedActivityLifecycleCallbacks());
// Delete any previous temp files created by the app
File tempFolder = new File(getApplicationContext().getCacheDir(), "mmShare");
RealPathUtil.deleteTempFiles(tempFolder);
Log.i("ReactNative", "Cleaning temp cache " + tempFolder.getAbsolutePath());
// Create an object of the custom facade impl
notificationsLifecycleFacade = NotificationsLifecycleFacade.getInstance();
// Attach it to react-native-navigation
setActivityCallbacks(notificationsLifecycleFacade);
SoLoader.init(this, /* native exopackage */ false);
}
// Uncomment to listen to react markers for build that has telemetry enabled
// addReactMarkerListener();
@Override
public boolean clearHostOnActivityDestroy(Activity activity) {
// This solves the issue where the splash screen does not go away
// after the app is killed by the OS cause of memory or a long time in the background
return false;
}
@Override
@@ -227,91 +110,9 @@ public class MainApplication extends NavigationApplication implements INotificat
return new CustomPushNotification(
context,
bundle,
defaultFacade,
notificationsLifecycleFacade, // Instead of defaultFacade!!!
defaultAppLaunchHelper,
new JsIOHelper()
);
}
@Override
public IPushNotificationsDrawer getPushNotificationsDrawer(Context context, AppLaunchHelper defaultAppLaunchHelper) {
return new CustomPushNotificationDrawer(context, defaultAppLaunchHelper);
}
public ReactContext getRunningReactContext() {
final ReactGateway reactGateway = getReactGateway();
if (reactGateway == null) {
return null;
}
return reactGateway
.getReactNativeHost()
.getReactInstanceManager()
.getCurrentReactContext();
}
public synchronized Bundle loadManagedConfig(Context ctx) {
if (ctx != null) {
RestrictionsManager myRestrictionsMgr =
(RestrictionsManager) ctx.getSystemService(Context.RESTRICTIONS_SERVICE);
mManagedConfig = myRestrictionsMgr.getApplicationRestrictions();
myRestrictionsMgr = null;
if (mManagedConfig!= null && mManagedConfig.size() > 0) {
return mManagedConfig;
}
return null;
}
return null;
}
public synchronized Bundle getManagedConfig() {
if (mManagedConfig!= null && mManagedConfig.size() > 0) {
return mManagedConfig;
}
ReactContext ctx = getRunningReactContext();
if (ctx != null) {
return loadManagedConfig(ctx);
}
return null;
}
private void addReactMarkerListener() {
ReactMarker.addListener(new ReactMarker.MarkerListener() {
@Override
public void logMarker(ReactMarkerConstants name, @Nullable String tag, int instanceKey) {
if (name.toString() == ReactMarkerConstants.RELOAD.toString()) {
APP_START_TIME = System.currentTimeMillis();
RELOAD = System.currentTimeMillis();
} else if (name.toString() == ReactMarkerConstants.PROCESS_PACKAGES_START.toString()) {
PROCESS_PACKAGES_START = System.currentTimeMillis();
} else if (name.toString() == ReactMarkerConstants.PROCESS_PACKAGES_END.toString()) {
PROCESS_PACKAGES_END = System.currentTimeMillis();
} else if (name.toString() == ReactMarkerConstants.CONTENT_APPEARED.toString()) {
CONTENT_APPEARED = System.currentTimeMillis();
ReactContext ctx = getRunningReactContext();
if (ctx != null) {
WritableMap map = Arguments.createMap();
map.putDouble("appReload", RELOAD);
map.putDouble("appContentAppeared", CONTENT_APPEARED);
map.putDouble("processPackagesStart", PROCESS_PACKAGES_START);
map.putDouble("processPackagesEnd", PROCESS_PACKAGES_END);
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).
emit("nativeMetrics", map);
}
}
}
});
}
}

View File

@@ -1,145 +0,0 @@
package com.mattermost.rnbeta;
import android.os.Bundle;
import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.content.Context;
import android.content.RestrictionsManager;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.util.ArraySet;
import android.util.Log;
import java.util.Set;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
public class ManagedActivityLifecycleCallbacks implements ActivityLifecycleCallbacks {
private static final String TAG = ManagedActivityLifecycleCallbacks.class.getSimpleName();
private final IntentFilter restrictionsFilter =
new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
private final BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context ctx, Intent intent) {
if (ctx != null) {
Bundle managedConfig = MainApplication.instance.loadManagedConfig(ctx);
// Check current configuration settings, change your app's UI and
// functionality as necessary.
Log.i(TAG, "Managed Configuration Changed");
sendConfigChanged(managedConfig);
}
}
};
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
MattermostManagedModule managedModule = MattermostManagedModule.getInstance();
if (managedModule != null && managedModule.isBlurAppScreenEnabled() && activity != null) {
activity.getWindow().setFlags(LayoutParams.FLAG_SECURE,
LayoutParams.FLAG_SECURE);
}
Bundle managedConfig = MainApplication.instance.getManagedConfig();
if (managedConfig != null && activity != null) {
activity.registerReceiver(restrictionsReceiver, restrictionsFilter);
}
}
@Override
public void onActivityResumed(Activity activity) {
ReactContext ctx = MainApplication.instance.getRunningReactContext();
Bundle managedConfig = MainApplication.instance.getManagedConfig();
if (ctx != null) {
Bundle newConfig = MainApplication.instance.loadManagedConfig(ctx);
if (!equalBundles(newConfig, managedConfig)) {
Log.i(TAG, "onResumed Managed Configuration Changed");
sendConfigChanged(newConfig);
}
}
}
@Override
public void onActivityStopped(Activity activity) {
Bundle managedConfig = MainApplication.instance.getManagedConfig();
if (managedConfig != null) {
try {
activity.unregisterReceiver(restrictionsReceiver);
} catch (IllegalArgumentException e) {
// Just ignore this cause the receiver wasn't registered for this activity
}
}
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
private void sendConfigChanged(Bundle config) {
WritableMap result = Arguments.createMap();
if (config != null) {
result = Arguments.fromBundle(config);
}
ReactContext ctx = MainApplication.instance.getRunningReactContext();
if (ctx != null) {
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("managedConfigDidChange", result);
}
}
private boolean equalBundles(Bundle one, Bundle two) {
if (one == null || two == null)
return false;
if(one.size() != two.size())
return false;
Set<String> setOne = new ArraySet<String>();
setOne.addAll(one.keySet());
setOne.addAll(two.keySet());
Object valueOne;
Object valueTwo;
for(String key : setOne) {
if (!one.containsKey(key) || !two.containsKey(key))
return false;
valueOne = one.get(key);
valueTwo = two.get(key);
if(valueOne instanceof Bundle && valueTwo instanceof Bundle &&
!equalBundles((Bundle) valueOne, (Bundle) valueTwo)) {
return false;
}
else if(valueOne == null) {
if(valueTwo != null)
return false;
}
else if(!valueOne.equals(valueTwo))
return false;
}
return true;
}
}

View File

@@ -1,41 +0,0 @@
package com.mattermost.rnbeta;
import android.content.Context;
import java.util.ArrayList;
import java.util.HashMap;
import com.facebook.react.bridge.ReactApplicationContext;
import com.oblador.keychain.KeychainModule;
import com.mattermost.react_native_interface.ResolvePromise;
import com.mattermost.react_native_interface.AsyncStorageHelper;
import com.mattermost.react_native_interface.KeysReadableArray;
public class MattermostCredentialsHelper {
static final String CURRENT_SERVER_URL = "@currentServerUrl";
public static void getCredentialsForCurrentServer(ReactApplicationContext context, ResolvePromise promise) {
final KeychainModule keychainModule = new KeychainModule(context);
final AsyncStorageHelper asyncStorage = new AsyncStorageHelper(context);
final ArrayList<String> keys = new ArrayList<String>(1);
keys.add(CURRENT_SERVER_URL);
KeysReadableArray asyncStorageKeys = new KeysReadableArray() {
@Override
public int size() {
return keys.size();
}
@Override
public String getString(int index) {
return keys.get(index);
}
};
HashMap<String, String> asyncStorageResults = asyncStorage.multiGet(asyncStorageKeys);
String serverUrl = asyncStorageResults.get(CURRENT_SERVER_URL);
keychainModule.getGenericPasswordForOptions(serverUrl, promise);
}
}

View File

@@ -1,11 +1,8 @@
package com.mattermost.rnbeta;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.NativeModule;
@@ -14,7 +11,6 @@ import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
public class MattermostManagedModule extends ReactContextBaseJavaModule {
private static MattermostManagedModule instance;
@@ -54,46 +50,16 @@ public class MattermostManagedModule extends ReactContextBaseJavaModule {
@ReactMethod
public void getConfig(final Promise promise) {
try {
Bundle config = MainApplication.instance.getManagedConfig();
Bundle config = NotificationsLifecycleFacade.getInstance().getManagedConfig();
if (config != null) {
Object result = Arguments.fromBundle(config);
promise.resolve(result);
} else {
promise.resolve(Arguments.createMap());
throw new Exception("The MDM vendor has not sent any Managed configuration");
}
} catch (Exception e) {
promise.resolve(Arguments.createMap());
promise.reject("no managed configuration", e);
}
}
@ReactMethod
// Close the current activity and open the security settings.
public void goToSecuritySettings() {
Intent intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getReactApplicationContext().startActivity(intent);
getCurrentActivity().finish();
System.exit(0);
}
@ReactMethod
public void isRunningInSplitView(final Promise promise) {
WritableMap result = Arguments.createMap();
Activity current = getCurrentActivity();
if (current != null) {
result.putBoolean("isSplitView", current.isInMultiWindowMode());
} else {
result.putBoolean("isSplitView", false);
}
promise.resolve(result);
}
@ReactMethod
public void quitApp() {
getCurrentActivity().finish();
System.exit(0);
}
}

View File

@@ -0,0 +1,32 @@
package com.mattermost.rnbeta;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.JavaScriptModule;
public class MattermostPackage implements ReactPackage {
private final MainApplication mApplication;
public MattermostPackage(MainApplication application) {
mApplication = application;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(
MattermostManagedModule.getInstance(reactContext),
NotificationPreferencesModule.getInstance(mApplication, reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList();
}
}

View File

@@ -11,7 +11,7 @@ import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
public class NotificationDismissService extends IntentService {
private Context mContext;
public NotificationDismissService() {
super("notificationDismissService");
super("notificationDismissService");
}
@Override

View File

@@ -1,15 +1,12 @@
package com.mattermost.rnbeta;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.database.Cursor;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.os.Bundle;
import android.net.Uri;
import android.service.notification.StatusBarNotification;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.NativeModule;
@@ -108,29 +105,4 @@ public class NotificationPreferencesModule extends ReactContextBaseJavaModule {
public void setShouldBlink(boolean blink) {
mNotificationPreference.setShouldBlink(blink);
}
@ReactMethod
public void getDeliveredNotifications(final Promise promise) {
Context context = mApplication.getApplicationContext();
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
StatusBarNotification[] statusBarNotifications = notificationManager.getActiveNotifications();
WritableArray result = Arguments.createArray();
for (StatusBarNotification sbn:statusBarNotifications) {
WritableMap map = Arguments.createMap();
Notification n = sbn.getNotification();
Bundle bundle = n.extras;
int identifier = sbn.getId();
String channelId = bundle.getString("channel_id");
map.putInt("identifier", identifier);
map.putString("channel_id", channelId);
result.pushMap(map);
}
promise.resolve(result);
}
@ReactMethod
public void removeDeliveredNotifications(int identifier, String channelId) {
Context context = mApplication.getApplicationContext();
CustomPushNotification.clearNotification(context, identifier, channelId);
}
}

View File

@@ -1,154 +0,0 @@
package com.mattermost.rnbeta;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.RemoteInput;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import com.mattermost.react_native_interface.ResolvePromise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
private Context mContext;
private Bundle bundle;
private NotificationManager notificationManager;
@Override
public void onReceive(Context context, Intent intent) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
final CharSequence message = getReplyMessage(intent);
if (message == null) {
return;
}
mContext = context;
bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
final int notificationId = intent.getIntExtra(CustomPushNotification.NOTIFICATION_ID, -1);
MattermostCredentialsHelper.getCredentialsForCurrentServer(reactApplicationContext, new ResolvePromise() {
@Override
public void resolve(@Nullable Object value) {
if (value instanceof Boolean && !(Boolean)value) {
return;
}
WritableMap map = (WritableMap) value;
if (map != null) {
String token = map.getString("password");
String serverUrl = map.getString("service");
Log.i("ReactNative", String.format("URL=%s TOKEN=%s", serverUrl, token));
replyToMessage(serverUrl, token, notificationId, message);
}
}
});
}
}
protected void replyToMessage(final String serverUrl, final String token, final int notificationId, final CharSequence message) {
final String channelId = bundle.getString("channel_id");
final String postId = bundle.getString("post_id");
String rootId = bundle.getString("root_id");
if (android.text.TextUtils.isEmpty(rootId)) {
rootId = postId;
}
if (token == null || serverUrl == null) {
onReplyFailed(notificationManager, notificationId, channelId);
return;
}
final OkHttpClient client = new OkHttpClient();
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
String json = buildReplyPost(channelId, rootId, message.toString());
Log.i("ReactNative", String.format("JSON STRING %s", json));
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.header("Authorization", String.format("Bearer %s", token))
.header("Content-Type", "application/json")
.url(String.format("%s/api/v4/posts", serverUrl.replaceAll("/$", "")))
.post(body)
.build();
client.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i("ReactNative", String.format("Reply with message %s FAILED exception %s", message, e.getMessage()));
onReplyFailed(notificationManager, notificationId, channelId);
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
onReplySuccess(notificationManager, notificationId, channelId);
Log.i("ReactNative", String.format("Reply with message %s", message));
} else {
Log.i("ReactNative", String.format("Reply with message %s FAILED status %s BODY %s", message, response.code(), response.body().string()));
onReplyFailed(notificationManager, notificationId, channelId);
}
}
});
}
protected String buildReplyPost(String channelId, String rootId, String message) {
return "{"
+ "\"channel_id\": \"" + channelId + "\","
+ "\"message\": \"" + message + "\","
+ "\"root_id\": \"" + rootId + "\""
+ "}";
}
protected void onReplyFailed(NotificationManager notificationManager, int notificationId, String channelId) {
String CHANNEL_ID = "Reply job";
Resources res = mContext.getResources();
String packageName = mContext.getPackageName();
int smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
notificationManager.createNotificationChannel(channel);
Notification notification =
new Notification.Builder(mContext, CHANNEL_ID)
.setContentTitle("Message failed to send.")
.setSmallIcon(smallIconResId)
.build();
CustomPushNotification.clearNotification(mContext, notificationId, channelId);
notificationManager.notify(notificationId, notification);
}
protected void onReplySuccess(NotificationManager notificationManager, int notificationId, String channelId) {
notificationManager.cancel(notificationId);
CustomPushNotification.clearNotification(mContext, notificationId, channelId);
}
private CharSequence getReplyMessage(Intent intent) {
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
return remoteInput.getCharSequence(CustomPushNotification.KEY_TEXT_REPLY);
}
return null;
}
}

View File

@@ -0,0 +1,81 @@
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.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
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();
if (CustomPushNotification.KEY_TEXT_REPLY.equals(intent.getAction())) {
CharSequence message = getReplyMessage(intent);
Bundle bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
String channelId = bundle.getString("channel_id");
bundle.putCharSequence("text", message);
bundle.putInt("msg_count", CustomPushNotification.getMessageCountInChannel(channelId));
int notificationId = intent.getIntExtra(CustomPushNotification.NOTIFICATION_ID, -1);
CustomPushNotification.clearNotification(mContext, notificationId, channelId);
MainApplication app = (MainApplication) this.getApplication();
app.replyFromPushNotification = true;
Log.i("ReactNative", "Replying service");
return new HeadlessJsTaskConfig(
"notificationReplied",
Arguments.fromBundle(bundle),
5000);
}
return null;
}
private CharSequence getReplyMessage(Intent intent) {
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
return remoteInput.getCharSequence(CustomPushNotification.KEY_TEXT_REPLY);
}
return null;
}
}

View File

@@ -0,0 +1,251 @@
package com.mattermost.rnbeta;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.RestrictionsManager;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.util.ArraySet;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.content.res.Configuration;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.reactnativenavigation.NavigationApplication;
import com.reactnativenavigation.controllers.ActivityCallbacks;
import com.reactnativenavigation.react.ReactGateway;
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class NotificationsLifecycleFacade extends ActivityCallbacks implements AppLifecycleFacade {
private static final String TAG = NotificationsLifecycleFacade.class.getSimpleName();
private static NotificationsLifecycleFacade instance;
private Bundle managedConfig = null;
private Activity mVisibleActivity;
private Set<AppVisibilityListener> mListeners = new CopyOnWriteArraySet<>();
private final IntentFilter restrictionsFilter =
new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
private final BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
if (context != null) {
// Get the current configuration bundle
RestrictionsManager myRestrictionsMgr =
(RestrictionsManager) context
.getSystemService(Context.RESTRICTIONS_SERVICE);
managedConfig = myRestrictionsMgr.getApplicationRestrictions();
// Check current configuration settings, change your app's UI and
// functionality as necessary.
Log.i("ReactNative", "Managed Configuration Changed");
sendConfigChanged(managedConfig);
}
}
};
public static NotificationsLifecycleFacade getInstance() {
if (instance == null) {
instance = new NotificationsLifecycleFacade();
}
return instance;
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
MattermostManagedModule managedModule = MattermostManagedModule.getInstance();
if (managedModule != null && managedModule.isBlurAppScreenEnabled() && activity != null) {
activity.getWindow().setFlags(LayoutParams.FLAG_SECURE,
LayoutParams.FLAG_SECURE);
}
if (managedConfig != null && managedConfig.size() > 0 && activity != null) {
activity.registerReceiver(restrictionsReceiver, restrictionsFilter);
}
if (activity != null) {
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
}
@Override
public void onActivityResumed(Activity activity) {
switchToVisible(activity);
ReactContext ctx = getRunningReactContext();
if (managedConfig != null && managedConfig.size() > 0 && ctx != null) {
RestrictionsManager myRestrictionsMgr =
(RestrictionsManager) ctx
.getSystemService(Context.RESTRICTIONS_SERVICE);
Bundle newConfig = myRestrictionsMgr.getApplicationRestrictions();
if (!equalBundles(newConfig ,managedConfig)) {
Log.i("ReactNative", "onResumed Managed Configuration Changed");
managedConfig = newConfig;
sendConfigChanged(managedConfig);
}
}
}
@Override
public void onActivityPaused(Activity activity) {
switchToInvisible(activity);
}
@Override
public void onActivityStopped(Activity activity) {
switchToInvisible(activity);
if (managedConfig != null && managedConfig.size() > 0) {
try {
activity.unregisterReceiver(restrictionsReceiver);
} catch (IllegalArgumentException e) {
// Just ignore this cause the receiver wasn't registered for this activity
}
}
}
@Override
public void onActivityDestroyed(Activity activity) {
switchToInvisible(activity);
}
@Override
public boolean isReactInitialized() {
return NavigationApplication.instance.isReactContextInitialized();
}
@Override
public ReactContext getRunningReactContext() {
final ReactGateway reactGateway = NavigationApplication.instance.getReactGateway();
if (reactGateway == null || !reactGateway.isInitialized()) {
return null;
}
return reactGateway.getReactContext();
}
@Override
public boolean isAppVisible() {
return mVisibleActivity != null;
}
@Override
public synchronized void addVisibilityListener(AppVisibilityListener listener) {
mListeners.add(listener);
}
@Override
public synchronized void removeVisibilityListener(AppVisibilityListener listener) {
mListeners.remove(listener);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mVisibleActivity != null) {
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
mVisibleActivity.sendBroadcast(intent);
}
}
private synchronized void switchToVisible(Activity activity) {
if (mVisibleActivity == null) {
mVisibleActivity = activity;
Log.v(TAG, "Activity is now visible ("+activity+")");
for (AppVisibilityListener listener : mListeners) {
listener.onAppVisible();
}
}
}
private synchronized void switchToInvisible(Activity activity) {
if (mVisibleActivity == activity) {
mVisibleActivity = null;
Log.v(TAG, "Activity is now NOT visible ("+activity+")");
for (AppVisibilityListener listener : mListeners) {
listener.onAppNotVisible();
}
}
}
public synchronized void LoadManagedConfig(ReactContext ctx) {
if (ctx != null) {
RestrictionsManager myRestrictionsMgr =
(RestrictionsManager) ctx
.getSystemService(Context.RESTRICTIONS_SERVICE);
managedConfig = myRestrictionsMgr.getApplicationRestrictions();
myRestrictionsMgr = null;
}
}
public synchronized Bundle getManagedConfig() {
if (managedConfig!= null && managedConfig.size() > 0) {
return managedConfig;
}
ReactContext ctx = getRunningReactContext();
if (ctx != null) {
LoadManagedConfig(ctx);
return managedConfig;
}
return null;
}
public void sendConfigChanged(Bundle config) {
Object result = Arguments.fromBundle(config);
ReactContext ctx = getRunningReactContext();
if (ctx != null) {
ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).
emit("managedConfigDidChange", result);
}
}
private boolean equalBundles(Bundle one, Bundle two) {
if (one == null || two == null)
return false;
if(one.size() != two.size())
return false;
Set<String> setOne = new ArraySet<String>();
setOne.addAll(one.keySet());
setOne.addAll(two.keySet());
Object valueOne;
Object valueTwo;
for(String key : setOne) {
if (!one.containsKey(key) || !two.containsKey(key))
return false;
valueOne = one.get(key);
valueTwo = two.get(key);
if(valueOne instanceof Bundle && valueTwo instanceof Bundle &&
!equalBundles((Bundle) valueOne, (Bundle) valueTwo)) {
return false;
}
else if(valueOne == null) {
if(valueTwo != null)
return false;
}
else if(!valueOne.equals(valueTwo))
return false;
}
return true;
}
}

View File

@@ -1,7 +0,0 @@
package com.mattermost.rnbeta;
import android.net.Uri;
public interface RNEditTextOnPasteListener {
void onPaste(Uri itemUri);
}

View File

@@ -1,92 +0,0 @@
package com.mattermost.rnbeta;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
public class RNPasteableActionCallback implements ActionMode.Callback {
private RNPasteableEditText mEditText;
RNPasteableActionCallback(RNPasteableEditText editText) {
mEditText = editText;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Bundle config = MainApplication.instance.getManagedConfig();
if (config != null) {
WritableMap result = Arguments.fromBundle(config);
String copyPasteProtection = result.getString("copyAndPasteProtection");
if (copyPasteProtection.equals("true")) {
disableMenus(menu);
}
}
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Uri uri = this.getUriInClipboard();
if (item.getItemId() == android.R.id.paste && uri != null) {
mEditText.getOnPasteListener().onPaste(uri);
mode.finish();
} else {
mEditText.onTextContextMenuItem(item.getItemId());
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
private void disableMenus(Menu menu) {
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
int id = item.getItemId();
boolean shouldDisableMenu = (
id == android.R.id.paste
|| id == android.R.id.copy
|| id == android.R.id.cut
);
item.setEnabled(!shouldDisableMenu);
}
}
private Uri getUriInClipboard() {
ClipboardManager clipboardManager = (ClipboardManager) mEditText.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboardManager.getPrimaryClip();
if (clipData == null) {
return null;
}
ClipData.Item item = clipData.getItemAt(0);
if (item == null) {
return null;
}
String text = item.getText().toString();
if (text.length() > 0) {
return null;
}
return item.getUri();
}
}

View File

@@ -1,22 +0,0 @@
package com.mattermost.rnbeta;
import android.content.Context;
import com.facebook.react.views.textinput.ReactEditText;
public class RNPasteableEditText extends ReactEditText {
private RNEditTextOnPasteListener mOnPasteListener;
public RNPasteableEditText(Context context) {
super(context);
}
public void setOnPasteListener(RNEditTextOnPasteListener listener) {
mOnPasteListener = listener;
}
public RNEditTextOnPasteListener getOnPasteListener() {
return mOnPasteListener;
}
}

View File

@@ -1,122 +0,0 @@
package com.mattermost.rnbeta;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import android.util.Patterns;
import android.webkit.MimeTypeMap;
import android.webkit.URLUtil;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.mattermost.share.RealPathUtil;
import java.io.FileNotFoundException;
import java.util.regex.Matcher;
public class RNPasteableEditTextOnPasteListener implements RNEditTextOnPasteListener {
private RNPasteableEditText mEditText;
RNPasteableEditTextOnPasteListener(RNPasteableEditText editText) {
mEditText = editText;
}
@Override
public void onPaste(Uri itemUri) {
ReactContext reactContext = (ReactContext)mEditText.getContext();
String uri = itemUri.toString();
WritableArray images = null;
WritableMap error = null;
String uriMimeType = reactContext.getContentResolver().getType(itemUri);
if (uriMimeType == null) {
return;
}
// Special handle for Google docs
if (uri.equals("content://com.google.android.apps.docs.editors.kix.editors.clipboard")) {
ClipboardManager clipboardManager = (ClipboardManager) reactContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboardManager.getPrimaryClip();
if (clipData == null) {
return;
}
ClipData.Item item = clipData.getItemAt(0);
String htmlText = item.getHtmlText();
// Find uri from html
Matcher matcher = Patterns.WEB_URL.matcher(htmlText);
if (matcher.find()) {
uri = htmlText.substring(matcher.start(1), matcher.end());
}
}
if (uri.startsWith("http")) {
Thread pastImageFromUrlThread = new Thread(new RNPasteableImageFromUrl(reactContext, mEditText, uri));
pastImageFromUrlThread.start();
return;
}
uri = RealPathUtil.getRealPathFromURI(reactContext, itemUri);
if (uri == null) {
return;
}
// Get type
String extension = MimeTypeMap.getFileExtensionFromUrl(uri);
if (extension == null) {
return;
}
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mimeType == null) {
return;
}
// Get fileName
String fileName = URLUtil.guessFileName(uri, null, mimeType);
// Get fileSize
long fileSize;
try {
ContentResolver contentResolver = reactContext.getContentResolver();
AssetFileDescriptor assetFileDescriptor = contentResolver.openAssetFileDescriptor(itemUri, "r");
if (assetFileDescriptor == null) {
return;
}
fileSize = assetFileDescriptor.getLength();
WritableMap image = Arguments.createMap();
image.putString("type", mimeType);
image.putDouble("fileSize", fileSize);
image.putString("fileName", fileName);
image.putString("uri", "file://" + uri);
images = Arguments.createArray();
images.pushMap(image);
} catch (FileNotFoundException e) {
error = Arguments.createMap();
error.putString("message", e.getMessage());
}
WritableMap event = Arguments.createMap();
event.putArray("data", images);
event.putMap("error", error);
reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(
mEditText.getId(),
"onPaste",
event
);
}
}

View File

@@ -1,76 +0,0 @@
package com.mattermost.rnbeta;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.react.views.textinput.ReactEditText;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
public class RNPasteableImageFromUrl implements Runnable {
private ReactContext mContext;
private String mUri;
private ReactEditText mTarget;
RNPasteableImageFromUrl(ReactContext context, ReactEditText target, String uri) {
mContext = context;
mUri = uri;
mTarget = target;
}
@Override
public void run() {
WritableArray images = null;
WritableMap error = null;
try {
URL url = new URL(mUri);
URLConnection u = url.openConnection();
// Get type
String mimeType = u.getHeaderField("Content-Type");
if (!mimeType.startsWith("image")) {
return;
}
// Get fileSize
long fileSize = Long.parseLong(u.getHeaderField("Content-Length"));
// Get fileName
String contentDisposition = u.getHeaderField("Content-Disposition");
int startIndex = contentDisposition.indexOf("filename=\"") + 10;
int endIndex = contentDisposition.length() - 1;
String fileName = contentDisposition.substring(startIndex, endIndex);
WritableMap image = Arguments.createMap();
image.putString("type", mimeType);
image.putDouble("fileSize", fileSize);
image.putString("fileName", fileName);
image.putString("uri", mUri);
images = Arguments.createArray();
images.pushMap(image);
} catch (IOException e) {
error = Arguments.createMap();
error.putString("message", e.getMessage());
}
WritableMap event = Arguments.createMap();
event.putArray("data", images);
event.putMap("error", error);
mContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(
mTarget.getId(),
"onPaste",
event
);
}
}

View File

@@ -1,78 +0,0 @@
package com.mattermost.rnbeta;
import android.support.v13.view.inputmethod.EditorInfoCompat;
import android.support.v13.view.inputmethod.InputConnectionCompat;
import android.support.v4.os.BuildCompat;
import android.text.InputType;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.views.textinput.ReactEditText;
import com.facebook.react.views.textinput.ReactTextInputManager;
import java.util.Map;
import javax.annotation.Nullable;
public class RNPasteableTextInputManager extends ReactTextInputManager {
@Override
public String getName() {
return "PasteableTextInputAndroid";
}
@Override
public ReactEditText createViewInstance(ThemedReactContext context) {
RNPasteableEditText editText = new RNPasteableEditText(context) {
@Override
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
final InputConnection ic = super.onCreateInputConnection(editorInfo);
EditorInfoCompat.setContentMimeTypes(editorInfo,
new String [] {"image/*"});
final InputConnectionCompat.OnCommitContentListener callback =
(inputContentInfo, flags, opts) -> {
// read and display inputContentInfo asynchronously
if (BuildCompat.isAtLeastNMR1() && (flags &
InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
try {
inputContentInfo.requestPermission();
}
catch (Exception e) {
return false;
}
}
this.getOnPasteListener().onPaste(inputContentInfo.getContentUri());
return true;
};
return InputConnectionCompat.createWrapper(ic, editorInfo, callback);
}
};
int inputType = editText.getInputType();
editText.setInputType(inputType & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
editText.setReturnKeyType("done");
editText.setCustomInsertionActionModeCallback(new RNPasteableActionCallback(editText));
editText.setCustomSelectionActionModeCallback(new RNPasteableActionCallback(editText));
return editText;
}
@Override
protected void addEventEmitters(ThemedReactContext reactContext, ReactEditText editText) {
super.addEventEmitters(reactContext, editText);
RNPasteableEditText pasteableEditText = (RNPasteableEditText)editText;
pasteableEditText.setOnPasteListener(new RNPasteableEditTextOnPasteListener(pasteableEditText));
}
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
Map map = super.getExportedViewConstants();
map.put("onPaste", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onPaste")));
return map;
}
}

View File

@@ -1,28 +0,0 @@
package com.mattermost.rnbeta;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nonnull;
public class RNPasteableTextInputPackage implements ReactPackage {
@Nonnull
@Override
public List<NativeModule> createNativeModules(@Nonnull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Nonnull
@Override
public List<ViewManager> createViewManagers(@Nonnull ReactApplicationContext reactContext) {
return Arrays.asList(
new RNPasteableTextInputManager()
);
}
}

View File

@@ -1,42 +0,0 @@
package com.mattermost.rnbeta;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import android.content.Context;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
public class RNTextInputResetModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext reactContext;
public RNTextInputResetModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@Override
public String getName() {
return "RNTextInputReset";
}
// https://github.com/facebook/react-native/pull/12462#issuecomment-298812731
@ReactMethod
public void resetKeyboardInput(final int reactTagToReset) {
UIManagerModule uiManager = getReactApplicationContext().getNativeModule(UIManagerModule.class);
uiManager.addUIBlock(new UIBlock() {
@Override
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
InputMethodManager imm = (InputMethodManager) getReactApplicationContext().getBaseContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
View viewToReset = nativeViewHierarchyManager.resolveView(reactTagToReset);
imm.restartInput(viewToReset);
}
}
});
}
}

View File

@@ -1,95 +0,0 @@
package com.mattermost.rnbeta;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.Log;
import java.lang.System;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.HttpUrl;
import org.json.JSONObject;
import org.json.JSONException;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.mattermost.react_native_interface.ResolvePromise;
public class ReceiptDelivery {
static final String CURRENT_SERVER_URL = "@currentServerUrl";
public static void send (Context context, final String ackId, final String type) {
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
MattermostCredentialsHelper.getCredentialsForCurrentServer(reactApplicationContext, new ResolvePromise() {
@Override
public void resolve(@Nullable Object value) {
if (value instanceof Boolean && !(Boolean)value) {
return;
}
WritableMap map = (WritableMap) value;
if (map != null) {
String token = map.getString("password");
String serverUrl = map.getString("service");
if (serverUrl.isEmpty()) {
String[] credentials = token.split(",[ ]*");
if (credentials.length == 2) {
token = credentials[0];
serverUrl = credentials[1];
}
}
Log.i("ReactNative", String.format("Send receipt delivery ACK=%s TYPE=%s to URL=%s with TOKEN=%s", ackId, type, serverUrl, token));
execute(serverUrl, token, ackId, type);
}
}
});
}
protected static void execute(String serverUrl, String token, String ackId, String type) {
if (token == null || serverUrl == null) {
return;
}
JSONObject json;
long receivedAt = System.currentTimeMillis();
try {
json = new JSONObject();
json.put("id", ackId);
json.put("received_at", receivedAt);
json.put("platform", "android");
json.put("type", type);
} catch (JSONException e) {
Log.e("ReactNative", "Receipt delivery failed to build json payload");
return;
}
final HttpUrl url = HttpUrl.parse(
String.format("%s/api/v4/notifications/ack", serverUrl.replaceAll("/$", "")));
if (url != null) {
final OkHttpClient client = new OkHttpClient();
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, json.toString());
Request request = new Request.Builder()
.header("Authorization", String.format("Bearer %s", token))
.header("Content-Type", "application/json")
.url(url)
.post(body)
.build();
try {
client.newCall(request).execute();
} catch (Exception e) {
Log.e("ReactNative", "Receipt delivery failed to send");
}
}
}
}

View File

@@ -45,7 +45,9 @@ public class RealPathUtil {
return id.replaceFirst("raw:", "");
}
try {
return getPathFromSavingTempFile(context, uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
} catch (NumberFormatException e) {
Log.e("ReactNative", "DownloadsProvider unexpected uri " + uri.toString());
return null;
@@ -205,12 +207,8 @@ public class RealPathUtil {
}
public static String getMimeTypeFromUri(final Context context, final Uri uri) {
try {
ContentResolver cR = context.getContentResolver();
return cR.getType(uri);
} catch (Exception e) {
return "application/octet-stream";
}
ContentResolver cR = context.getContentResolver();
return cR.getType(uri);
}
public static void deleteTempFiles(final File dir) {

View File

@@ -75,12 +75,9 @@ public class ShareModule extends ReactContextBaseJavaModule {
@ReactMethod
public void close(ReadableMap data) {
this.clear();
Activity currentActivity = getCurrentActivity();
if (currentActivity != null) {
currentActivity.finish();
}
getCurrentActivity().finish();
if (data != null && data.hasKey("url")) {
if (data != null) {
ReadableArray files = data.getArray("files");
String serverUrl = data.getString("url");
String token = data.getString("token");
@@ -148,19 +145,17 @@ public class ShareModule extends ReactContextBaseJavaModule {
items.pushMap(map);
} else if (Intent.ACTION_SEND.equals(action)) {
Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (uri != null) {
text = "file://" + RealPathUtil.getRealPathFromURI(currentActivity, uri);
map.putString("value", text);
text = "file://" + RealPathUtil.getRealPathFromURI(currentActivity, uri);
map.putString("value", text);
if (type.equals("image/*")) {
type = "image/jpeg";
} else if (type.equals("video/*")) {
type = "video/mp4";
}
map.putString("type", type);
items.pushMap(map);
if (type.equals("image/*")) {
type = "image/jpeg";
} else if (type.equals("video/*")) {
type = "video/mp4";
}
map.putString("type", type);
items.pushMap(map);
} else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
for (Uri uri : uris) {
@@ -170,15 +165,12 @@ public class ShareModule extends ReactContextBaseJavaModule {
map.putString("value", text);
type = RealPathUtil.getMimeTypeFromUri(currentActivity, uri);
if (type != null) {
if (type.equals("image/*")) {
type = "image/jpeg";
} else if (type.equals("video/*")) {
type = "video/mp4";
}
} else {
type = "application/octet-stream";
if (type.equals("image/*")) {
type = "image/jpeg";
} else if (type.equals("video/*")) {
type = "video/mp4";
}
map.putString("type", type);
items.pushMap(map);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 413 B

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 B

After

Width:  |  Height:  |  Size: 925 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

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