Merge branch 'develop' into single-server
# Conflicts: # README.md # android/app/src/debug/AndroidManifest.xml # android/gradle.properties # app/views/RoomsListView/Header/Header.js # ios/RocketChatRN.xcodeproj/project.pbxproj # ios/RocketChatRN/RocketChatRN.entitlements
This commit is contained in:
commit
4e437e3269
|
@ -75,29 +75,6 @@ restore_cache: &restore-gradle-cache
|
|||
name: Restore gradle cache
|
||||
key: android-{{ checksum "android/build.gradle" }}-{{ checksum "android/app/build.gradle" }}
|
||||
|
||||
restore-brew-cache: &restore-brew-cache
|
||||
name: Restore Brew cache
|
||||
key: brew-{{ checksum "yarn.lock" }}-{{ checksum ".circleci/config.yml" }}
|
||||
|
||||
save-brew-cache: &save-brew-cache
|
||||
name: Save brew cache
|
||||
key: brew-{{ checksum "yarn.lock" }}-{{ checksum ".circleci/config.yml" }}
|
||||
paths:
|
||||
- /usr/local/Homebrew
|
||||
|
||||
install-apple-sim-utils: &install-apple-sim-utils
|
||||
name: Install appleSimUtils
|
||||
command: |
|
||||
brew update
|
||||
brew tap wix/brew
|
||||
brew install wix/brew/applesimutils
|
||||
|
||||
rebuild-detox: &rebuild-detox
|
||||
name: Rebuild Detox framework cache
|
||||
command: |
|
||||
npx detox clean-framework-cache
|
||||
npx detox build-framework-cache
|
||||
|
||||
version: 2.1
|
||||
|
||||
# EXECUTORS
|
||||
|
@ -107,35 +84,6 @@ executors:
|
|||
environment:
|
||||
<<: *bash-env
|
||||
|
||||
# COMMANDS
|
||||
commands:
|
||||
detox-test:
|
||||
parameters:
|
||||
folder:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- attach_workspace:
|
||||
at: .
|
||||
|
||||
- restore_cache: *restore-npm-cache-mac
|
||||
|
||||
- restore_cache: *restore-brew-cache
|
||||
|
||||
- run: *install-node
|
||||
|
||||
- run: *install-apple-sim-utils
|
||||
|
||||
- run: *install-npm-modules
|
||||
|
||||
- run: *rebuild-detox
|
||||
|
||||
- run:
|
||||
name: Test
|
||||
command: |
|
||||
npx detox test << parameters.folder >> --configuration ios.sim.release --cleanup
|
||||
|
||||
# JOBS
|
||||
jobs:
|
||||
lint-testunit:
|
||||
|
@ -170,57 +118,6 @@ jobs:
|
|||
|
||||
- save_cache: *save-npm-cache-linux
|
||||
|
||||
# E2E
|
||||
e2e-build:
|
||||
executor: mac-env
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- restore_cache: *restore-npm-cache-mac
|
||||
|
||||
- restore_cache: *restore-brew-cache
|
||||
|
||||
- run: *install-node
|
||||
|
||||
- run: *install-apple-sim-utils
|
||||
|
||||
- run: *install-npm-modules
|
||||
|
||||
- run: *rebuild-detox
|
||||
|
||||
- run:
|
||||
name: Build
|
||||
command: |
|
||||
npx detox build --configuration ios.sim.release
|
||||
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- ios/build/Build/Products/Release-iphonesimulator/RocketChatRN.app
|
||||
|
||||
- save_cache: *save-npm-cache-mac
|
||||
|
||||
- save_cache: *save-brew-cache
|
||||
|
||||
e2e-test-onboarding:
|
||||
executor: mac-env
|
||||
steps:
|
||||
- detox-test:
|
||||
folder: "./e2e/tests/onboarding"
|
||||
|
||||
e2e-test-room:
|
||||
executor: mac-env
|
||||
steps:
|
||||
- detox-test:
|
||||
folder: "./e2e/tests/room"
|
||||
|
||||
e2e-test-assorted:
|
||||
executor: mac-env
|
||||
steps:
|
||||
- detox-test:
|
||||
folder: "./e2e/tests/assorted"
|
||||
|
||||
# Android builds
|
||||
android-build:
|
||||
<<: *defaults
|
||||
|
@ -429,23 +326,6 @@ workflows:
|
|||
jobs:
|
||||
- lint-testunit
|
||||
|
||||
- e2e-hold:
|
||||
type: approval
|
||||
requires:
|
||||
- lint-testunit
|
||||
- e2e-build:
|
||||
requires:
|
||||
- e2e-hold
|
||||
- e2e-test-onboarding:
|
||||
requires:
|
||||
- e2e-build
|
||||
- e2e-test-room:
|
||||
requires:
|
||||
- e2e-build
|
||||
- e2e-test-assorted:
|
||||
requires:
|
||||
- e2e-build
|
||||
|
||||
- ios-build:
|
||||
requires:
|
||||
- lint-testunit
|
||||
|
|
|
@ -1,7 +1,34 @@
|
|||
<!-- INSTRUCTION: Keep the line below to notify all core developers about this new PR -->
|
||||
@RocketChat/ReactNative
|
||||
<!-- This is a pull request template, you do not need to uncomment or remove the comments, they won't show up in the PR text. -->
|
||||
|
||||
<!-- INSTRUCTION: Inform the issue number that this PR closes, or remove the line below -->
|
||||
Closes #ISSUE_NUMBER
|
||||
## Proposed changes
|
||||
<!-- Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue below. -->
|
||||
|
||||
<!-- INSTRUCTION: Tell us more about your PR with screen shots if you can -->
|
||||
## Issue(s)
|
||||
<!-- Link the issues being closed by or related to this PR. For example, you can use #594 if this PR closes issue number 594 -->
|
||||
|
||||
## How to test or reproduce
|
||||
<!-- Mention how you would reproduce the bug if not mentioned on the issue page already. Also mention which screens are going to have the changes if applicable -->
|
||||
|
||||
## Screenshots
|
||||
|
||||
## Types of changes
|
||||
<!-- What types of changes does your code introduce to Rocket.Chat? -->
|
||||
<!-- Put an `x` in the boxes that apply -->
|
||||
|
||||
- [ ] Bugfix (non-breaking change which fixes an issue)
|
||||
- [ ] Improvement (non-breaking change which improves a current function)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Documentation update (if none of the other choices apply)
|
||||
|
||||
## Checklist
|
||||
<!-- Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code. -->
|
||||
|
||||
- [ ] I have read the [CONTRIBUTING](https://github.com/RocketChat/Rocket.Chat/blob/develop/.github/CONTRIBUTING.md#contributing-to-rocketchat) doc
|
||||
- [ ] I have signed the [CLA](https://cla-assistant.io/RocketChat/Rocket.Chat.ReactNative)
|
||||
- [ ] Lint and unit tests pass locally with my changes
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works (if applicable)
|
||||
- [ ] I have added necessary documentation (if applicable)
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
|
||||
## Further comments
|
||||
<!-- If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... -->
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
name: iOS Detox
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
detox-build:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_11.5.app
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Generate Detox app cache key
|
||||
run: echo $(git rev-parse HEAD:app) > "./app-git-revision.txt"
|
||||
|
||||
- name: Cache Detox app
|
||||
uses: actions/cache@v1
|
||||
id: detoxappcache
|
||||
with:
|
||||
path: ios/build/Build/Products/Release-iphonesimulator
|
||||
key: iOSDetoxRelease-v4-${{ hashFiles('yarn.lock') }}-${{ hashFiles('ios/Podfile.lock') }}-${{ hashFiles('./app-git-revision.txt') }}
|
||||
|
||||
- name: Node
|
||||
if: steps.detoxappcache.outputs.cache-hit != 'true'
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Cache node modules
|
||||
if: steps.detoxappcache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v1
|
||||
id: npmcache
|
||||
with:
|
||||
path: node_modules
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Rebuild detox
|
||||
if: steps.detoxappcache.outputs.cache-hit != 'true' && steps.npmcache.outputs.cache-hit == 'true'
|
||||
run: yarn detox clean-framework-cache && yarn detox build-framework-cache
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.detoxappcache.outputs.cache-hit != 'true' && steps.npmcache.outputs.cache-hit != 'true'
|
||||
run: yarn install
|
||||
|
||||
- run: yarn detox build e2e --configuration ios.sim.release
|
||||
if: steps.detoxappcache.outputs.cache-hit != 'true'
|
||||
|
||||
detox-test-rooms:
|
||||
needs: detox-build
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_11.5.app
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Generate Detox app cache key
|
||||
run: echo $(git rev-parse HEAD:app) > "./app-git-revision.txt"
|
||||
|
||||
- name: Cache Detox app
|
||||
uses: actions/cache@v1
|
||||
id: detoxappcache
|
||||
with:
|
||||
path: ios/build/Build/Products/Release-iphonesimulator
|
||||
key: iOSDetoxRelease-v4-${{ hashFiles('yarn.lock') }}-${{ hashFiles('ios/Podfile.lock') }}-${{ hashFiles('./app-git-revision.txt') }}
|
||||
|
||||
- name: Check for Detox app
|
||||
if: steps.detoxappcache.outputs.cache-hit != 'true'
|
||||
run: exit 1
|
||||
|
||||
- name: Node
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v1
|
||||
id: npmcache
|
||||
with:
|
||||
path: node_modules
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Rebuild detox
|
||||
if: steps.npmcache.outputs.cache-hit == 'true'
|
||||
run: yarn detox clean-framework-cache && yarn detox build-framework-cache
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.npmcache.outputs.cache-hit != 'true'
|
||||
run: yarn install
|
||||
|
||||
- run: brew tap wix/brew
|
||||
- run: brew install applesimutils
|
||||
- run: yarn detox test e2e/tests/room --configuration ios.sim.release --cleanup
|
||||
|
||||
- name: Upload test artifacts
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: artifacts
|
||||
path: artifacts
|
||||
|
||||
detox-test-assorted:
|
||||
needs: detox-build
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_11.5.app
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Generate Detox app cache key
|
||||
run: echo $(git rev-parse HEAD:app) > "./app-git-revision.txt"
|
||||
|
||||
- name: Cache Detox app
|
||||
uses: actions/cache@v1
|
||||
id: detoxappcache
|
||||
with:
|
||||
path: ios/build/Build/Products/Release-iphonesimulator
|
||||
key: iOSDetoxRelease-v4-${{ hashFiles('yarn.lock') }}-${{ hashFiles('ios/Podfile.lock') }}-${{ hashFiles('./app-git-revision.txt') }}
|
||||
|
||||
- name: Check for Detox app
|
||||
if: steps.detoxappcache.outputs.cache-hit != 'true'
|
||||
run: exit 1
|
||||
|
||||
- name: Node
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v1
|
||||
id: npmcache
|
||||
with:
|
||||
path: node_modules
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Rebuild detox
|
||||
if: steps.npmcache.outputs.cache-hit == 'true'
|
||||
run: yarn detox clean-framework-cache && yarn detox build-framework-cache
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.npmcache.outputs.cache-hit != 'true'
|
||||
run: yarn install
|
||||
|
||||
- run: brew tap wix/brew
|
||||
- run: brew install applesimutils
|
||||
- run: yarn detox test e2e/tests/assorted --configuration ios.sim.release --cleanup
|
||||
|
||||
- name: Upload test artifacts
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: artifacts
|
||||
path: artifacts
|
||||
|
||||
detox-test-onboarding:
|
||||
needs: detox-build
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_11.5.app
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Generate Detox app cache key
|
||||
run: echo $(git rev-parse HEAD:app) > "./app-git-revision.txt"
|
||||
|
||||
- name: Cache Detox app
|
||||
uses: actions/cache@v1
|
||||
id: detoxappcache
|
||||
with:
|
||||
path: ios/build/Build/Products/Release-iphonesimulator
|
||||
key: iOSDetoxRelease-v4-${{ hashFiles('yarn.lock') }}-${{ hashFiles('ios/Podfile.lock') }}-${{ hashFiles('./app-git-revision.txt') }}
|
||||
|
||||
- name: Check for Detox app
|
||||
if: steps.detoxappcache.outputs.cache-hit != 'true'
|
||||
run: exit 1
|
||||
|
||||
- name: Node
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v1
|
||||
id: npmcache
|
||||
with:
|
||||
path: node_modules
|
||||
key: node-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Rebuild detox
|
||||
if: steps.npmcache.outputs.cache-hit == 'true'
|
||||
run: yarn detox clean-framework-cache && yarn detox build-framework-cache
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.npmcache.outputs.cache-hit != 'true'
|
||||
run: yarn install
|
||||
|
||||
- run: brew tap wix/brew
|
||||
- run: brew install applesimutils
|
||||
- run: yarn detox test e2e/tests/onboarding --configuration ios.sim.release --cleanup
|
||||
|
||||
- name: Upload test artifacts
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: artifacts
|
||||
path: artifacts
|
|
@ -58,6 +58,7 @@ buck-out/
|
|||
|
||||
coverage
|
||||
|
||||
artifacts
|
||||
.vscode/
|
||||
e2e/docker/rc_test_env/docker-compose.yml
|
||||
e2e/docker/data/db
|
|
@ -0,0 +1,93 @@
|
|||
# Contributing Guidelines
|
||||
|
||||
Great to have you here! Here are a few ways you can help make this project better!
|
||||
|
||||
## Setting up a development environment
|
||||
|
||||
Refer to [React Native environment setup](https://reactnative.dev/docs/environment-setup) to make sure everything is up and running.
|
||||
Follow the `React Native CLI Quickstart` section as we don't support Expo managed flow.
|
||||
|
||||
*Note: you'll need a MacOS to run iOS apps*
|
||||
|
||||
### How to run
|
||||
|
||||
Clone repository and install dependencies:
|
||||
```sh
|
||||
git clone git@github.com:RocketChat/Rocket.Chat.ReactNative.git
|
||||
cd Rocket.Chat.ReactNative
|
||||
yarn
|
||||
```
|
||||
|
||||
Run the app:
|
||||
```sh
|
||||
npx react-native run-ios
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
npx react-native run-android
|
||||
```
|
||||
|
||||
At this point, the app should be running on the simulator or on your device!
|
||||
|
||||
*Note: npm won't work on this project*
|
||||
|
||||
## Issues needing help
|
||||
|
||||
Didn't find a bug or want a new feature not already reported? Check out the [help wanted](https://github.com/RocketChat/Rocket.Chat.ReactNative/issues?q=is%3Aissue+is%3Aopen+label%3A%22%F0%9F%91%8B+help+wanted%22) or the [good first issue](https://github.com/RocketChat/Rocket.Chat.ReactNative/issues?q=is%3Aissue+is%3Aopen+label%3A%22%F0%9F%8D%AD+good+first+issue%22) labels.
|
||||
|
||||
Can't help coding? Triaging issues is a **great** way of helping.
|
||||
|
||||
## Code style
|
||||
|
||||
We use [ESLint](https://eslint.org/) to enforce code style and best practices. We have a pre-commit hook enforcing commits to follow our lint rules.
|
||||
|
||||
To check for lint issues on your code, run this on your terminal:
|
||||
|
||||
```sh
|
||||
yarn lint
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
It's always important to ensure everything is working properly and that's why tests are great. We have unit and e2e tests on this project.
|
||||
|
||||
### Unit tests
|
||||
|
||||
We use [Jest](https://jestjs.io/) and [Storybook](https://storybook.js.org/) on our tests.
|
||||
|
||||
#### Storybook
|
||||
|
||||
Storybook is a tool for developing UI Components and has some plugins to make Jest generate snapshots of them.
|
||||
|
||||
[On the root of the project](https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/index.js#L24), comment everything leaving only the last import to Storybook left and refresh your project.
|
||||
You'll see some tests like this:
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/804994/89677725-56393200-d8c4-11ea-84b0-213be1d24e98.png" width="350" />
|
||||
|
||||
#### Jest
|
||||
|
||||
We use Jest for our unit tests and to generate Storybook snapshots. We have a pre-commit hook enforcing preventing commits that breaks any test.
|
||||
|
||||
To check for test issues on your code, run this on your terminal:
|
||||
|
||||
```sh
|
||||
yarn test
|
||||
```
|
||||
|
||||
### E2E tests
|
||||
|
||||
We use [Detox](https://github.com/wix/Detox) framework to end-to-end test our app and ensure everything is working properly.
|
||||
|
||||
[Follow this documentation to learn how to run it](https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/e2e).
|
||||
|
||||
### Pull request
|
||||
|
||||
As soon as your changes are ready, you can open a Pull Request.
|
||||
|
||||
The title of your PR should be descriptive, including either [NEW], [IMPROVEMENT] or [FIX] at the beginning, e.g. [FIX] App crashing on startup.
|
||||
|
||||
You may share working results prior to finishing, please include [WIP] in the title. This way anyone can look at your code: you can ask for help within the PR if you don't know how to solve a problem.
|
||||
|
||||
Your PR is automatically inspected by various tools, check their response and try to improve your code accordingly. Requests that fail to build or have wrong coding style won't be merged.
|
225
README.md
225
README.md
|
@ -5,11 +5,12 @@
|
|||
[![codecov](https://codecov.io/gh/RocketChat/Rocket.Chat.ReactNative/branch/master/graph/badge.svg)](https://codecov.io/gh/RocketChat/Rocket.Chat.ReactNative)
|
||||
[![CodeFactor](https://www.codefactor.io/repository/github/rocketchat/rocket.chat.reactnative/badge)](https://www.codefactor.io/repository/github/rocketchat/rocket.chat.reactnative)
|
||||
|
||||
**Supported Server Versions:** 0.70.0+
|
||||
- **Supported server versions:** 0.70.0+
|
||||
- **Supported iOS versions**: 11+
|
||||
- **Supported Android versions**: 5.0+
|
||||
|
||||
## Download
|
||||
|
||||
### Official apps
|
||||
<a href="https://play.google.com/store/apps/details?id=chat.rocket.android">
|
||||
<img alt="Download on Google Play" src="https://play.google.com/intl/en_us/badges/images/badge_new.png" height=43>
|
||||
</a>
|
||||
|
@ -17,29 +18,7 @@
|
|||
<img alt="Download on App Store" src="https://user-images.githubusercontent.com/7317008/43209852-4ca39622-904b-11e8-8ce1-cdc3aee76ae9.png" height=43>
|
||||
</a>
|
||||
|
||||
### Experimental apps
|
||||
<a href="https://play.google.com/store/apps/details?id=chat.rocket.reactnative">
|
||||
<img alt="Download on Google Play" src="https://play.google.com/intl/en_us/badges/images/badge_new.png" height=43>
|
||||
</a>
|
||||
<a href="https://itunes.apple.com/us/app/rocket-chat-experimental/id1272915472">
|
||||
<img alt="Download on App Store" src="https://user-images.githubusercontent.com/7317008/43209852-4ca39622-904b-11e8-8ce1-cdc3aee76ae9.png" height=43>
|
||||
</a>
|
||||
|
||||
## Beta Access
|
||||
|
||||
### TestFlight
|
||||
|
||||
You can signup to our TestFlight builds by accessing these links:
|
||||
|
||||
- Official: https://testflight.apple.com/join/3gcYeoMr
|
||||
- Experimental: https://testflight.apple.com/join/7I3dLCNT.
|
||||
|
||||
### Google Play beta
|
||||
|
||||
You can subscribe to Google Play Beta program and download latest versions:
|
||||
|
||||
- Official: https://play.google.com/store/apps/details?id=chat.rocket.android
|
||||
- Experimental: https://play.google.com/store/apps/details?id=chat.rocket.reactnative
|
||||
Check [our docs](https://docs.rocket.chat/installation/mobile-and-desktop-apps#mobile-apps) for beta and Experimental versions.
|
||||
|
||||
## Reporting an Issue
|
||||
|
||||
|
@ -47,194 +26,16 @@ You can subscribe to Google Play Beta program and download latest versions:
|
|||
|
||||
Also check the [#react-native](https://open.rocket.chat/channel/react-native) community on [open.rocket.chat](https://open.rocket.chat). We'd like to help.
|
||||
|
||||
## Installing dependencies
|
||||
## Contributing
|
||||
|
||||
Follow the [React Native Getting Started Guide](https://facebook.github.io/react-native/docs/getting-started.html) for detailed instructions on setting up your local machine for development.
|
||||
Are you a dev and would like to help? Found a bug that you would like to report or a missing feature that you would like to work on? Great! We have written down a [Contribution guide](https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/CONTRIBUTING.md) so you can start easily.
|
||||
|
||||
## How to run
|
||||
- Clone repository and install dependencies:
|
||||
```bash
|
||||
$ git clone git@github.com:RocketChat/Rocket.Chat.ReactNative.git
|
||||
$ cd Rocket.Chat.ReactNative
|
||||
$ yarn
|
||||
```
|
||||
## Whitelabel
|
||||
Do you want to make the app run on your own server only? [Follow our whitelabel documentation.](https://docs.rocket.chat/guides/developer/mobile-apps/whitelabeling-mobile-apps)
|
||||
|
||||
- Run application
|
||||
```bash
|
||||
$ npx react-native run-ios
|
||||
```
|
||||
```bash
|
||||
$ npx react-native run-android
|
||||
```
|
||||
## Engage with us
|
||||
### Share your story
|
||||
We’d love to hear about [your experience](https://survey.zohopublic.com/zs/e4BUFG) and potentially feature it on our [Blog](https://rocket.chat/case-studies/?utm_source=github&utm_medium=readme&utm_campaign=community).
|
||||
|
||||
### Whitelabel
|
||||
Follow our docs: https://docs.rocket.chat/guides/developer/mobile-apps/whitelabeling-mobile-apps
|
||||
|
||||
## Current priorities
|
||||
1) Omnichannel support
|
||||
2) E2E encryption
|
||||
|
||||
## Features
|
||||
| Feature | Status |
|
||||
|--------------------------------------------------------------- |-------- |
|
||||
| Jitsi Integration | ✅ |
|
||||
| Federation (Directory) | ✅ |
|
||||
| Discussions | ✅ |
|
||||
| Omnichannel | ❌ |
|
||||
| Threads | ✅ |
|
||||
| Record Audio | ✅ |
|
||||
| Record Video | ✅ |
|
||||
| Commands | ✅ |
|
||||
| Draft message per room | ✅ |
|
||||
| Share Extension | ✅ |
|
||||
| Notifications Preferences | ✅ |
|
||||
| Edited status | ✅ |
|
||||
| Upload video | ✅ |
|
||||
| Grouped messages | ✅ |
|
||||
| Mark room as read | ✅ |
|
||||
| Mark room as unread | ✅ |
|
||||
| Tablet Support | ✅ |
|
||||
| Read receipt | ✅ |
|
||||
| Broadbast Channel | ✅ |
|
||||
| Authentication via SAML | ✅ |
|
||||
| Authentication via CAS | ✅ |
|
||||
| Custom Fields on Signup | ✅ |
|
||||
| Report message | ✅ |
|
||||
| Theming | ✅ |
|
||||
| Settings -> Review the App | ✅ |
|
||||
| Settings -> Default Browser | ✅ |
|
||||
| Admin panel | ✅ |
|
||||
| Reply message from notification | ✅ |
|
||||
| Unread counter banner on message list | ✅ |
|
||||
| E2E Encryption | ❌ |
|
||||
| Join a Protected Room | ❌ |
|
||||
| Optional Analytics | ✅ |
|
||||
| Settings -> About us | ❌ |
|
||||
| Settings -> Contact us | ✅ |
|
||||
| Settings -> Update App Icon | ❌ |
|
||||
| Settings -> Share | ✅ |
|
||||
| Accessibility (Medium) | ❌ |
|
||||
| Accessibility (Advanced) | ❌ |
|
||||
| Authentication via Meteor | ❌ |
|
||||
| Authentication via Wordpress | ✅ |
|
||||
| Authentication via Custom OAuth | ✅ |
|
||||
| Add user to the room | ✅ |
|
||||
| Send message | ✅ |
|
||||
| Authentication via Email | ✅ |
|
||||
| Authentication via Username | ✅ |
|
||||
| Authentication via LDAP | ✅ |
|
||||
| Message format: Markdown | ✅ |
|
||||
| Message format: System messages (Welcome, Message removed...) | ✅ |
|
||||
| Message format: links | ✅ |
|
||||
| Message format: images | ✅ |
|
||||
| Message format: replies | ✅ |
|
||||
| Message format: alias with custom message (title & text) | ✅ |
|
||||
| Messages list: day separation | ✅ |
|
||||
| Messages list: load more on scroll | ✅ |
|
||||
| Messages list: receive new messages via subscription | ✅ |
|
||||
| Subscriptions list | ✅ |
|
||||
| Segmented subscriptions list: Favorites | ✅ |
|
||||
| Segmented subscriptions list: Unreads | ✅ |
|
||||
| Segmented subscriptions list: DMs | ✅ |
|
||||
| Segmented subscriptions list: Channels | ✅ |
|
||||
| Subscriptions list: update user status via subscription | ✅ |
|
||||
| Numbers os messages unread in the Subscriptions list | ✅ |
|
||||
| Status change | ✅ |
|
||||
| Upload image | ✅ |
|
||||
| Take picture & upload it | ✅ |
|
||||
| 2FA | ✅ |
|
||||
| Signup | ✅ |
|
||||
| Autocomplete with usernames | ✅ |
|
||||
| Autocomplete with @all & @here | ✅ |
|
||||
| Autocomplete room/channel name | ✅ |
|
||||
| Upload audio | ✅ |
|
||||
| Forgot your password | ✅ |
|
||||
| Login screen: terms of service | ✅ |
|
||||
| Login screen: privacy policy | ✅ |
|
||||
| Authentication via Google | ✅ |
|
||||
| Authentication via Facebook | ✅ |
|
||||
| Authentication via Twitter | ✅ |
|
||||
| Authentication via GitHub | ✅ |
|
||||
| Authentication via GitLab | ✅ |
|
||||
| Authentication via LinkedIn | ✅ |
|
||||
| Create channel | ✅ |
|
||||
| Search Local | ✅ |
|
||||
| Search in the API | ✅ |
|
||||
| Settings -> License | ✅ |
|
||||
| Settings -> App version | ✅ |
|
||||
| Autocomplete emoji | ✅ |
|
||||
| Upload file (documents, PDFs, spreadsheets, zip files, etc) | ✅ |
|
||||
| Copy message | ✅ |
|
||||
| Pin message | ✅ |
|
||||
| Unpin message | ✅ |
|
||||
| Channel Info screen -> Members | ✅ |
|
||||
| Channel Info screen -> Pinned | ✅ |
|
||||
| Channel Info screen -> Starred | ✅ |
|
||||
| Channel Info screen -> Uploads | ✅ |
|
||||
| Star message | ✅ |
|
||||
| Unstar message | ✅ |
|
||||
| Channel Info screen -> Topic | ✅ |
|
||||
| Channel Info screen -> Description | ✅ |
|
||||
| Star a channel | ✅ |
|
||||
| Message format: videos | ✅ |
|
||||
| Message format: audios | ✅ |
|
||||
| Edit message | ✅ |
|
||||
| Delete a message | ✅ |
|
||||
| Reply message | ✅ |
|
||||
| Quote message | ✅ |
|
||||
| Muted state | ✅ |
|
||||
| Offline reading | ✅ |
|
||||
| Offline writing | ✅ |
|
||||
| Edit profile | ✅ |
|
||||
| Reactions | ✅ |
|
||||
| Custom emojis | ✅ |
|
||||
| Accessibility (Basic) | ✅ |
|
||||
| Tap notification, go to the channel | ✅ |
|
||||
| Deep links: Authentication | ✅ |
|
||||
| Deep links: Rooms | ✅ |
|
||||
| Full name setting | ✅ |
|
||||
| Read only rooms | ✅ |
|
||||
| Typing status | ✅ |
|
||||
| Create channel/group | ✅ |
|
||||
| Disable registration setting | ✅ |
|
||||
| Unread red line indicator on message list | ✅ |
|
||||
| Search Messages in Channel | ✅ |
|
||||
| Mentions List | ✅ |
|
||||
| Attachment List | ✅ |
|
||||
| Join a Room | ✅ |
|
||||
|
||||
## Detox (end-to-end tests)
|
||||
- Build your app
|
||||
|
||||
```bash
|
||||
$ npx detox build --configuration ios.sim.release
|
||||
```
|
||||
|
||||
- Run tests
|
||||
|
||||
```bash
|
||||
$ npx detox test ./e2e/tests/onboarding --configuration ios.sim.release
|
||||
$ npx detox test ./e2e/tests/room --configuration ios.sim.release
|
||||
$ npx detox test ./e2e/tests/assorted --configuration ios.sim.release
|
||||
```
|
||||
|
||||
## Storybook
|
||||
- Open index.js
|
||||
|
||||
- Uncomment following line
|
||||
|
||||
```bash
|
||||
import './storybook';
|
||||
```
|
||||
|
||||
- Comment out following lines
|
||||
```bash
|
||||
import './app/ReactotronConfig';
|
||||
import { AppRegistry } from 'react-native';
|
||||
import App from './app/index';
|
||||
import { name as appName } from './app.json';
|
||||
|
||||
AppRegistry.registerComponent(appName, () => App);
|
||||
```
|
||||
|
||||
- Start your application again
|
||||
### Subscribe for Updates
|
||||
Once a month our marketing team releases an email update with news about product releases, company related topics, events and use cases. [Sign Up!](https://rocket.chat/newsletter/?utm_source=github&utm_medium=readme&utm_campaign=community)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
crashlytics: null
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
|||
apply plugin: "com.android.application"
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: "io.fabric"
|
||||
apply plugin: "com.google.firebase.firebase-perf"
|
||||
apply plugin: 'com.bugsnag.android.gradle'
|
||||
|
||||
import com.android.build.OutputFile
|
||||
|
@ -168,15 +168,18 @@ android {
|
|||
minifyEnabled enableProguardInReleaseBuilds
|
||||
setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'])
|
||||
signingConfig signingConfigs.release
|
||||
firebaseCrashlytics {
|
||||
nativeSymbolUploadEnabled true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst '**/armeabi-v7a/libc++_shared.so'
|
||||
pickFirst '**/x86/libc++_shared.so'
|
||||
pickFirst '**/arm64-v8a/libc++_shared.so'
|
||||
pickFirst '**/x86_64/libc++_shared.so'
|
||||
}
|
||||
// packagingOptions {
|
||||
// pickFirst '**/armeabi-v7a/libc++_shared.so'
|
||||
// pickFirst '**/x86/libc++_shared.so'
|
||||
// pickFirst '**/arm64-v8a/libc++_shared.so'
|
||||
// pickFirst '**/x86_64/libc++_shared.so'
|
||||
// }
|
||||
|
||||
// applicationVariants are e.g. debug, release
|
||||
applicationVariants.all { variant ->
|
||||
|
@ -215,11 +218,6 @@ dependencies {
|
|||
//noinspection GradleDynamicVersion
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
implementation "com.google.firebase:firebase-messaging:18.0.0"
|
||||
implementation "com.google.firebase:firebase-core:16.0.9"
|
||||
implementation "com.google.firebase:firebase-perf:17.0.2"
|
||||
implementation('com.crashlytics.sdk.android:crashlytics:2.9.9@aar') {
|
||||
transitive = true
|
||||
}
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.fbjni'
|
||||
|
@ -251,5 +249,4 @@ task copyDownloadableDepsToLibs(type: Copy) {
|
|||
into 'libs'
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="chat.rocket.reactnative">
|
||||
|
||||
<application
|
||||
android:name=".MainDebugApplication"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:replace="android:name"
|
||||
tools:targetApi="28" />
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
</manifest>
|
|
@ -0,0 +1,25 @@
|
|||
package chat.rocket.reactnative;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
|
||||
public class MainDebugApplication extends MainApplication {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
|
||||
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
||||
*
|
||||
* @param context
|
||||
* @param reactInstanceManager
|
||||
*/
|
||||
private static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||
ReactNativeFlipper.initializeFlipper(context, reactInstanceManager);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.rndiffapp;
|
||||
package chat.rocket.reactnative;
|
||||
import android.content.Context;
|
||||
import com.facebook.flipper.android.AndroidFlipperClient;
|
||||
import com.facebook.flipper.android.utils.FlipperUtils;
|
||||
|
|
|
@ -37,8 +37,10 @@
|
|||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" android:host="go.rocket.chat" />
|
||||
<data android:scheme="https" android:host="jitsi.rocket.chat" />
|
||||
<data android:scheme="rocketchat" android:host="room" />
|
||||
<data android:scheme="rocketchat" android:host="auth" />
|
||||
<data android:scheme="rocketchat" android:host="jitsi.rocket.chat" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,10 @@
|
|||
package chat.rocket.reactnative;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class Callback {
|
||||
public void call(@Nullable Bundle bundle) {
|
||||
|
||||
}
|
||||
}
|
|
@ -14,8 +14,9 @@ import android.graphics.drawable.Icon;
|
|||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.app.Person;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.Gson;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
|
@ -33,15 +34,18 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
|
||||
|
||||
public class CustomPushNotification extends PushNotification {
|
||||
public static ReactApplicationContext reactApplicationContext;
|
||||
final NotificationManager notificationManager;
|
||||
|
||||
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
|
||||
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
|
||||
reactApplicationContext = new ReactApplicationContext(context);
|
||||
notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
private static Map<String, List<Bundle>> notificationMessages = new HashMap<String, List<Bundle>>();
|
||||
|
@ -54,29 +58,39 @@ public class CustomPushNotification extends PushNotification {
|
|||
|
||||
@Override
|
||||
public void onReceived() throws InvalidNotificationException {
|
||||
final Bundle bundle = mNotificationProps.asBundle();
|
||||
Bundle received = mNotificationProps.asBundle();
|
||||
Ejson receivedEjson = new Gson().fromJson(received.getString("ejson", "{}"), Ejson.class);
|
||||
|
||||
if (receivedEjson.notificationType != null && receivedEjson.notificationType.equals("message-id-only")) {
|
||||
notificationLoad(receivedEjson, new Callback() {
|
||||
@Override
|
||||
public void call(@Nullable Bundle bundle) {
|
||||
if (bundle != null) {
|
||||
mNotificationProps = createProps(bundle);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// We should re-read these values since that can be changed by notificationLoad
|
||||
Bundle bundle = mNotificationProps.asBundle();
|
||||
Ejson loadedEjson = new Gson().fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
||||
String notId = bundle.getString("notId", "1");
|
||||
String title = bundle.getString("title");
|
||||
|
||||
if (notificationMessages.get(notId) == null) {
|
||||
notificationMessages.put(notId, new ArrayList<Bundle>());
|
||||
}
|
||||
|
||||
Gson gson = new Gson();
|
||||
Ejson ejson = gson.fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
||||
|
||||
boolean hasSender = ejson.sender != null;
|
||||
boolean hasSender = loadedEjson.sender != null;
|
||||
String title = bundle.getString("title");
|
||||
|
||||
bundle.putLong("time", new Date().getTime());
|
||||
bundle.putString("username", hasSender ? ejson.sender.username : title);
|
||||
bundle.putString("senderId", hasSender ? ejson.sender._id : "1");
|
||||
bundle.putString("avatarUri", ejson.getAvatarUri());
|
||||
bundle.putString("username", hasSender ? loadedEjson.sender.username : title);
|
||||
bundle.putString("senderId", hasSender ? loadedEjson.sender._id : "1");
|
||||
bundle.putString("avatarUri", loadedEjson.getAvatarUri());
|
||||
|
||||
notificationMessages.get(notId).add(bundle);
|
||||
|
||||
super.postNotification(Integer.parseInt(notId));
|
||||
|
||||
postNotification(Integer.parseInt(notId));
|
||||
notifyReceivedToJS();
|
||||
}
|
||||
|
||||
|
@ -96,9 +110,11 @@ public class CustomPushNotification extends PushNotification {
|
|||
String notId = bundle.getString("notId", "1");
|
||||
String title = bundle.getString("title");
|
||||
String message = bundle.getString("message");
|
||||
Boolean notificationLoaded = bundle.getBoolean("notificationLoaded", false);
|
||||
Ejson ejson = new Gson().fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
||||
|
||||
notification
|
||||
.setContentTitle(title)
|
||||
.setContentTitle(title)
|
||||
.setContentText(message)
|
||||
.setContentIntent(intent)
|
||||
.setPriority(Notification.PRIORITY_HIGH)
|
||||
|
@ -109,10 +125,34 @@ public class CustomPushNotification extends PushNotification {
|
|||
notificationColor(notification);
|
||||
notificationChannel(notification);
|
||||
notificationIcons(notification, bundle);
|
||||
notificationStyle(notification, notificationId, bundle);
|
||||
notificationReply(notification, notificationId, bundle);
|
||||
notificationDismiss(notification, notificationId);
|
||||
|
||||
// if notificationType is null (RC < 3.5) or notificationType is different of message-id-only or notification was loaded successfully
|
||||
if (ejson.notificationType == null || !ejson.notificationType.equals("message-id-only") || notificationLoaded) {
|
||||
notificationStyle(notification, notificationId, bundle);
|
||||
notificationReply(notification, notificationId, bundle);
|
||||
|
||||
// message couldn't be loaded from server (Fallback notification)
|
||||
} else {
|
||||
Gson gson = new Gson();
|
||||
// iterate over the current notification ids to dismiss fallback notifications from same server
|
||||
for (Map.Entry<String, List<Bundle>> bundleList : notificationMessages.entrySet()) {
|
||||
// iterate over the notifications with this id (same host + rid)
|
||||
Iterator iterator = bundleList.getValue().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Bundle not = (Bundle) iterator.next();
|
||||
// get the notification info
|
||||
Ejson notEjson = gson.fromJson(not.getString("ejson", "{}"), Ejson.class);
|
||||
// if already has a notification from same server
|
||||
if (ejson.serverURL().equals(notEjson.serverURL())) {
|
||||
String id = not.getString("notId");
|
||||
// cancel this notification
|
||||
notificationManager.cancel(Integer.parseInt(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
|
@ -300,4 +340,7 @@ public class CustomPushNotification extends PushNotification {
|
|||
notification.setDeleteIntent(dismissPendingIntent);
|
||||
}
|
||||
|
||||
private void notificationLoad(Ejson ejson, Callback callback) {
|
||||
LoadNotification.load(reactApplicationContext, ejson, callback);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ public class Ejson {
|
|||
String rid;
|
||||
String type;
|
||||
Sender sender;
|
||||
String messageId;
|
||||
String notificationType;
|
||||
|
||||
private String TOKEN_KEY = "reactnativemeteor_usertoken-";
|
||||
private SharedPreferences sharedPreferences = RNUserDefaultsModule.getPreferences(CustomPushNotification.reactApplicationContext);
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package chat.rocket.reactnative;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.content.Context;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.Interceptor;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
|
||||
import chat.rocket.userdefaults.RNUserDefaultsModule;
|
||||
|
||||
class JsonResponse {
|
||||
Data data;
|
||||
|
||||
class Data {
|
||||
Notification notification;
|
||||
|
||||
class Notification {
|
||||
String notId;
|
||||
String title;
|
||||
String text;
|
||||
Payload payload;
|
||||
|
||||
class Payload {
|
||||
String host;
|
||||
String rid;
|
||||
String type;
|
||||
Sender sender;
|
||||
String messageId;
|
||||
String notificationType;
|
||||
String name;
|
||||
String messageType;
|
||||
|
||||
class Sender {
|
||||
String _id;
|
||||
String username;
|
||||
String name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LoadNotification {
|
||||
private static int RETRY_COUNT = 0;
|
||||
private static int[] TIMEOUT = new int[]{ 0, 1, 3, 5, 10 };
|
||||
private static String TOKEN_KEY = "reactnativemeteor_usertoken-";
|
||||
|
||||
public static void load(ReactApplicationContext reactApplicationContext, final Ejson ejson, Callback callback) {
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
HttpUrl.Builder url = HttpUrl.parse(ejson.serverURL().concat("/api/v1/push.get")).newBuilder();
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.header("x-user-id", ejson.userId())
|
||||
.header("x-auth-token", ejson.token())
|
||||
.url(url.addQueryParameter("id", ejson.messageId).build())
|
||||
.build();
|
||||
|
||||
runRequest(client, request, callback);
|
||||
}
|
||||
|
||||
private static void runRequest(OkHttpClient client, Request request, Callback callback) {
|
||||
try {
|
||||
Thread.sleep(TIMEOUT[RETRY_COUNT] * 1000);
|
||||
|
||||
Response response = client.newCall(request).execute();
|
||||
String body = response.body().string();
|
||||
if (!response.isSuccessful()) {
|
||||
throw new Exception("Error");
|
||||
}
|
||||
|
||||
Gson gson = new Gson();
|
||||
JsonResponse json = gson.fromJson(body, JsonResponse.class);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("notId", json.data.notification.notId);
|
||||
bundle.putString("title", json.data.notification.title);
|
||||
bundle.putString("message", json.data.notification.text);
|
||||
bundle.putString("ejson", gson.toJson(json.data.notification.payload));
|
||||
bundle.putBoolean("notificationLoaded", true);
|
||||
|
||||
callback.call(bundle);
|
||||
|
||||
} catch (Exception e) {
|
||||
if (RETRY_COUNT <= TIMEOUT.length) {
|
||||
RETRY_COUNT++;
|
||||
runRequest(client, request, callback);
|
||||
} else {
|
||||
callback.call(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,10 +29,6 @@ import com.wix.reactnativenotifications.core.notification.INotificationsApplicat
|
|||
import com.wix.reactnativenotifications.core.notification.IPushNotification;
|
||||
import com.wix.reactnativekeyboardinput.KeyboardInputPackage;
|
||||
|
||||
import io.invertase.firebase.fabric.crashlytics.RNFirebaseCrashlyticsPackage;
|
||||
import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage;
|
||||
import io.invertase.firebase.perf.RNFirebasePerformancePackage;
|
||||
|
||||
import com.nozbe.watermelondb.WatermelonDBPackage;
|
||||
import com.reactnativecommunity.viewpager.RNCViewPagerPackage;
|
||||
|
||||
|
@ -53,9 +49,6 @@ public class MainApplication extends Application implements ReactApplication, IN
|
|||
protected List<ReactPackage> getPackages() {
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
List<ReactPackage> packages = new PackageList(this).getPackages();
|
||||
packages.add(new RNFirebaseCrashlyticsPackage());
|
||||
packages.add(new RNFirebaseAnalyticsPackage());
|
||||
packages.add(new RNFirebasePerformancePackage());
|
||||
packages.add(new KeyboardInputPackage(MainApplication.this));
|
||||
packages.add(new RNNotificationsPackage(MainApplication.this));
|
||||
packages.add(new WatermelonDBPackage());
|
||||
|
@ -88,38 +81,6 @@ public class MainApplication extends Application implements ReactApplication, IN
|
|||
public void onCreate() {
|
||||
super.onCreate();
|
||||
SoLoader.init(this, /* native exopackage */ false);
|
||||
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
|
||||
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
||||
*
|
||||
* @param context
|
||||
* @param reactInstanceManager
|
||||
*/
|
||||
private static void initializeFlipper(
|
||||
Context context, ReactInstanceManager reactInstanceManager) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
try {
|
||||
/*
|
||||
We use reflection here to pick up the class that initializes Flipper,
|
||||
since Flipper library is not available in release mode
|
||||
*/
|
||||
Class<?> aClass = Class.forName("chat.rocket.reactnative");
|
||||
aClass
|
||||
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
|
||||
.invoke(null, context, reactInstanceManager);
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "28.0.3"
|
||||
buildToolsVersion = "29.0.2"
|
||||
minSdkVersion = 21
|
||||
compileSdkVersion = 28
|
||||
targetSdkVersion = 28
|
||||
compileSdkVersion = 29
|
||||
targetSdkVersion = 29
|
||||
glideVersion = "4.9.0"
|
||||
kotlin_version = "1.3.50"
|
||||
supportLibVersion = "28.0.0"
|
||||
|
@ -18,10 +18,9 @@ buildscript {
|
|||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
classpath 'io.fabric.tools:gradle:1.28.1'
|
||||
classpath 'com.google.firebase:perf-plugin:1.2.1'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.+'
|
||||
|
||||
|
@ -57,10 +56,10 @@ subprojects { subproject ->
|
|||
afterEvaluate {
|
||||
if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion "28.0.3"
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.2"
|
||||
defaultConfig {
|
||||
targetSdkVersion 28
|
||||
targetSdkVersion 29
|
||||
}
|
||||
variantFilter { variant ->
|
||||
def names = variant.flavors*.name
|
||||
|
|
|
@ -28,11 +28,11 @@ android.enableJetifier=true
|
|||
APPLICATIONID=chat.rocket.reactnative
|
||||
VERSIONNAME=4.9.0
|
||||
VERSIONCODE=1
|
||||
BugsnagAPIKey=""
|
||||
BugsnagAPIKey=
|
||||
KEYSTORE=my-upload-key.keystore
|
||||
KEY_ALIAS=my-key-alias
|
||||
KEYSTORE_PASSWORD=
|
||||
KEY_PASSWORD=
|
||||
|
||||
# Version of flipper SDK to use with React Native
|
||||
FLIPPER_VERSION=0.33.1
|
||||
FLIPPER_VERSION=0.51.0
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
|
@ -154,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
|
@ -175,14 +175,9 @@ save () {
|
|||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -5,7 +5,7 @@
|
|||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem http://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
|||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ export const ROOMS = createRequestTypes('ROOMS', [
|
|||
'CLOSE_SEARCH_HEADER'
|
||||
]);
|
||||
export const ROOM = createRequestTypes('ROOM', ['SUBSCRIBE', 'UNSUBSCRIBE', 'LEAVE', 'DELETE', 'REMOVED', 'CLOSE', 'FORWARD', 'USER_TYPING']);
|
||||
export const INQUIRY = createRequestTypes('INQUIRY', [...defaultTypes, 'SET_ENABLED', 'RESET', 'QUEUE_ADD', 'QUEUE_UPDATE', 'QUEUE_REMOVE']);
|
||||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS', 'SET_MASTER_DETAIL']);
|
||||
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import * as types from './actionsTypes';
|
||||
|
||||
export function inquirySetEnabled(enabled) {
|
||||
return {
|
||||
type: types.INQUIRY.SET_ENABLED,
|
||||
enabled
|
||||
};
|
||||
}
|
||||
|
||||
export function inquiryReset() {
|
||||
return {
|
||||
type: types.INQUIRY.RESET
|
||||
};
|
||||
}
|
||||
|
||||
export function inquiryQueueAdd(inquiry) {
|
||||
return {
|
||||
type: types.INQUIRY.QUEUE_ADD,
|
||||
inquiry
|
||||
};
|
||||
}
|
||||
|
||||
export function inquiryQueueUpdate(inquiry) {
|
||||
return {
|
||||
type: types.INQUIRY.QUEUE_UPDATE,
|
||||
inquiry
|
||||
};
|
||||
}
|
||||
|
||||
export function inquiryQueueRemove(inquiryId) {
|
||||
return {
|
||||
type: types.INQUIRY.QUEUE_REMOVE,
|
||||
inquiryId
|
||||
};
|
||||
}
|
||||
|
||||
export function inquiryRequest() {
|
||||
return {
|
||||
type: types.INQUIRY.REQUEST
|
||||
};
|
||||
}
|
||||
|
||||
export function inquirySuccess(inquiries) {
|
||||
return {
|
||||
type: types.INQUIRY.SUCCESS,
|
||||
inquiries
|
||||
};
|
||||
}
|
||||
|
||||
export function inquiryFailure(error) {
|
||||
return {
|
||||
type: types.INQUIRY.FAILURE,
|
||||
error
|
||||
};
|
||||
}
|
|
@ -10,6 +10,16 @@ export const SWITCH_TRACK_COLOR = {
|
|||
true: '#2de0a5'
|
||||
};
|
||||
|
||||
const mentions = {
|
||||
unreadBackground: '#414852',
|
||||
mentionMeColor: '#f5455c',
|
||||
mentionMeBackground: '#ffe9ec',
|
||||
mentionGroupColor: '#f38c39',
|
||||
mentionGroupBackground: '#fde8d7',
|
||||
mentionOtherColor: '#b68d00',
|
||||
mentionOtherBackground: '#fff6d6'
|
||||
};
|
||||
|
||||
export const themes = {
|
||||
light: {
|
||||
backgroundColor: '#ffffff',
|
||||
|
@ -53,7 +63,8 @@ export const themes = {
|
|||
passcodeDotEmpty: '#CBCED1',
|
||||
passcodeDotFull: '#6C727A',
|
||||
previewBackground: '#1F2329',
|
||||
previewTintColor: '#ffffff'
|
||||
previewTintColor: '#ffffff',
|
||||
...mentions
|
||||
},
|
||||
dark: {
|
||||
backgroundColor: '#030b1b',
|
||||
|
@ -97,7 +108,8 @@ export const themes = {
|
|||
passcodeDotEmpty: '#CBCED1',
|
||||
passcodeDotFull: '#6C727A',
|
||||
previewBackground: '#030b1b',
|
||||
previewTintColor: '#ffffff'
|
||||
previewTintColor: '#ffffff',
|
||||
...mentions
|
||||
},
|
||||
black: {
|
||||
backgroundColor: '#000000',
|
||||
|
@ -141,6 +153,7 @@ export const themes = {
|
|||
passcodeDotEmpty: '#CBCED1',
|
||||
passcodeDotFull: '#6C727A',
|
||||
previewBackground: '#000000',
|
||||
previewTintColor: '#ffffff'
|
||||
previewTintColor: '#ffffff',
|
||||
...mentions
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const CustomEmoji = React.memo(({ baseUrl, emoji, style }) => (
|
||||
|
|
|
@ -28,7 +28,7 @@ export const CustomHeaderButtons = React.memo(props => (
|
|||
|
||||
export const DrawerButton = React.memo(({ navigation, testID, ...otherProps }) => (
|
||||
<CustomHeaderButtons left>
|
||||
<Item title='drawer' iconName='menu_hamburguer' onPress={navigation.toggleDrawer} testID={testID} {...otherProps} />
|
||||
<Item title='drawer' iconName='hamburguer' onPress={navigation.toggleDrawer} testID={testID} {...otherProps} />
|
||||
</CustomHeaderButtons>
|
||||
));
|
||||
|
||||
|
@ -36,7 +36,7 @@ export const CloseModalButton = React.memo(({
|
|||
navigation, testID, onPress = () => navigation.pop(), ...props
|
||||
}) => (
|
||||
<CustomHeaderButtons left>
|
||||
<Item title='close' iconName='Cross' onPress={onPress} testID={testID} {...props} />
|
||||
<Item title='close' iconName='close' onPress={onPress} testID={testID} {...props} />
|
||||
</CustomHeaderButtons>
|
||||
));
|
||||
|
||||
|
@ -44,14 +44,14 @@ export const CancelModalButton = React.memo(({ onPress, testID }) => (
|
|||
<CustomHeaderButtons left>
|
||||
{isIOS
|
||||
? <Item title={I18n.t('Cancel')} onPress={onPress} testID={testID} />
|
||||
: <Item title='close' iconName='Cross' onPress={onPress} testID={testID} />
|
||||
: <Item title='close' iconName='close' onPress={onPress} testID={testID} />
|
||||
}
|
||||
</CustomHeaderButtons>
|
||||
));
|
||||
|
||||
export const MoreButton = React.memo(({ onPress, testID }) => (
|
||||
<CustomHeaderButtons>
|
||||
<Item title='more' iconName='menu' onPress={onPress} testID={testID} />
|
||||
<Item title='more' iconName='kebab' onPress={onPress} testID={testID} />
|
||||
</CustomHeaderButtons>
|
||||
));
|
||||
|
||||
|
|
|
@ -90,6 +90,8 @@ const NotifierComponent = React.memo(({
|
|||
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('DrawerNavigator');
|
||||
} else {
|
||||
Navigation.navigate('RoomsListView');
|
||||
}
|
||||
goRoom({ item, isMasterDetail });
|
||||
hideNotification();
|
||||
|
@ -125,7 +127,7 @@ const NotifierComponent = React.memo(({
|
|||
hitSlop={BUTTON_HIT_SLOP}
|
||||
background={Touchable.SelectableBackgroundBorderless()}
|
||||
>
|
||||
<CustomIcon name='Cross' style={[styles.close, { color: themes[theme].titleText }]} size={20} />
|
||||
<CustomIcon name='close' style={[styles.close, { color: themes[theme].titleText }]} size={20} />
|
||||
</Touchable>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@ const InAppNotification = memo(() => {
|
|||
const state = Navigation.navigationRef.current?.getRootState();
|
||||
const route = getActiveRoute(state);
|
||||
if (payload.rid) {
|
||||
if (route?.name === 'RoomView' && route.params?.rid === payload.rid) {
|
||||
if ((route?.name === 'RoomView' && route.params?.rid === payload.rid) || route?.name === 'JitsiMeetView') {
|
||||
return;
|
||||
}
|
||||
Notifier.showNotification({
|
||||
|
|
|
@ -15,6 +15,7 @@ import OrSeparator from './OrSeparator';
|
|||
import Touch from '../utils/touch';
|
||||
import I18n from '../i18n';
|
||||
import random from '../utils/random';
|
||||
import { logEvent, events } from '../utils/log';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
|
||||
const BUTTON_HEIGHT = 48;
|
||||
|
@ -77,6 +78,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressFacebook = () => {
|
||||
logEvent(events.ENTER_WITH_FACEBOOK);
|
||||
const { services, server } = this.props;
|
||||
const { clientId } = services.facebook;
|
||||
const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth';
|
||||
|
@ -88,6 +90,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressGithub = () => {
|
||||
logEvent(events.ENTER_WITH_GITHUB);
|
||||
const { services, server } = this.props;
|
||||
const { clientId } = services.github;
|
||||
const endpoint = `https://github.com/login?client_id=${ clientId }&return_to=${ encodeURIComponent('/login/oauth/authorize') }`;
|
||||
|
@ -99,6 +102,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressGitlab = () => {
|
||||
logEvent(events.ENTER_WITH_GITLAB);
|
||||
const { services, server, Gitlab_URL } = this.props;
|
||||
const { clientId } = services.gitlab;
|
||||
const baseURL = Gitlab_URL ? Gitlab_URL.trim().replace(/\/*$/, '') : 'https://gitlab.com';
|
||||
|
@ -111,6 +115,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressGoogle = () => {
|
||||
logEvent(events.ENTER_WITH_GOOGLE);
|
||||
const { services, server } = this.props;
|
||||
const { clientId } = services.google;
|
||||
const endpoint = 'https://accounts.google.com/o/oauth2/auth';
|
||||
|
@ -122,6 +127,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressLinkedin = () => {
|
||||
logEvent(events.ENTER_WITH_LINKEDIN);
|
||||
const { services, server } = this.props;
|
||||
const { clientId } = services.linkedin;
|
||||
const endpoint = 'https://www.linkedin.com/oauth/v2/authorization';
|
||||
|
@ -133,6 +139,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressMeteor = () => {
|
||||
logEvent(events.ENTER_WITH_METEOR);
|
||||
const { services, server } = this.props;
|
||||
const { clientId } = services['meteor-developer'];
|
||||
const endpoint = 'https://www.meteor.com/oauth2/authorize';
|
||||
|
@ -143,6 +150,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressTwitter = () => {
|
||||
logEvent(events.ENTER_WITH_TWITTER);
|
||||
const { server } = this.props;
|
||||
const state = this.getOAuthState();
|
||||
const url = `${ server }/_oauth/twitter/?requestTokenAndRedirect=true&state=${ state }`;
|
||||
|
@ -150,6 +158,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressWordpress = () => {
|
||||
logEvent(events.ENTER_WITH_WORDPRESS);
|
||||
const { services, server } = this.props;
|
||||
const { clientId, serverURL } = services.wordpress;
|
||||
const endpoint = `${ serverURL }/oauth/authorize`;
|
||||
|
@ -161,6 +170,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressCustomOAuth = (loginService) => {
|
||||
logEvent(events.ENTER_WITH_CUSTOM_OAUTH);
|
||||
const { server } = this.props;
|
||||
const {
|
||||
serverURL, authorizePath, clientId, scope, service
|
||||
|
@ -175,6 +185,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressSaml = (loginService) => {
|
||||
logEvent(events.ENTER_WITH_SAML);
|
||||
const { server } = this.props;
|
||||
const { clientConfig } = loginService;
|
||||
const { provider } = clientConfig;
|
||||
|
@ -184,6 +195,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressCas = () => {
|
||||
logEvent(events.ENTER_WITH_CAS);
|
||||
const { server, CAS_login_url } = this.props;
|
||||
const ssoToken = random(17);
|
||||
const url = `${ CAS_login_url }?service=${ server }/_cas/${ ssoToken }`;
|
||||
|
@ -191,6 +203,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressAppleLogin = async() => {
|
||||
logEvent(events.ENTER_WITH_APPLE);
|
||||
try {
|
||||
const { fullName, email, identityToken } = await AppleAuthentication.signInAsync({
|
||||
requestedScopes: [
|
||||
|
@ -201,7 +214,7 @@ class LoginServices extends React.PureComponent {
|
|||
|
||||
await RocketChat.loginOAuthOrSso({ fullName, email, identityToken });
|
||||
} catch {
|
||||
// Do nothing
|
||||
logEvent(events.ENTER_WITH_APPLE_F);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ const HeaderFooter = React.memo(({ onReaction, theme }) => (
|
|||
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
theme={theme}
|
||||
>
|
||||
<CustomIcon name='add-reaction' size={24} color={themes[theme].bodyText} />
|
||||
<CustomIcon name='reaction-add' size={24} color={themes[theme].bodyText} />
|
||||
</Button>
|
||||
));
|
||||
HeaderFooter.propTypes = {
|
||||
|
|
|
@ -7,7 +7,7 @@ import moment from 'moment';
|
|||
import RocketChat from '../../lib/rocketchat';
|
||||
import database from '../../lib/database';
|
||||
import I18n from '../../i18n';
|
||||
import log from '../../utils/log';
|
||||
import log, { logEvent } from '../../utils/log';
|
||||
import Navigation from '../../lib/Navigation';
|
||||
import { getMessageTranslation } from '../message/utils';
|
||||
import { LISTENER } from '../Toast';
|
||||
|
@ -15,6 +15,7 @@ import EventEmitter from '../../utils/events';
|
|||
import { showConfirmationAlert } from '../../utils/info';
|
||||
import { useActionSheet } from '../ActionSheet';
|
||||
import Header, { HEADER_HEIGHT } from './Header';
|
||||
import events from '../../utils/log/events';
|
||||
|
||||
const MessageActions = React.memo(forwardRef(({
|
||||
room,
|
||||
|
@ -112,11 +113,18 @@ const MessageActions = React.memo(forwardRef(({
|
|||
|
||||
const getPermalink = message => RocketChat.getPermalinkMessage(message);
|
||||
|
||||
const handleReply = message => replyInit(message, true);
|
||||
const handleReply = (message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_REPLY);
|
||||
replyInit(message, true);
|
||||
};
|
||||
|
||||
const handleEdit = message => editInit(message);
|
||||
const handleEdit = (message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_EDIT);
|
||||
editInit(message);
|
||||
};
|
||||
|
||||
const handleCreateDiscussion = (message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_DISCUSSION);
|
||||
const params = { message, channel: room, showCloseModal: true };
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('ModalStackNavigator', { screen: 'CreateDiscussionView', params });
|
||||
|
@ -126,6 +134,7 @@ const MessageActions = React.memo(forwardRef(({
|
|||
};
|
||||
|
||||
const handleUnread = async(message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_UNREAD);
|
||||
const { id: messageId, ts } = message;
|
||||
const { rid } = room;
|
||||
try {
|
||||
|
@ -144,54 +153,66 @@ const MessageActions = React.memo(forwardRef(({
|
|||
Navigation.navigate('RoomsListView');
|
||||
}
|
||||
} catch (e) {
|
||||
logEvent(events.ROOM_MSG_ACTION_UNREAD_F);
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePermalink = async(message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_PERMALINK);
|
||||
try {
|
||||
const permalink = await getPermalink(message);
|
||||
Clipboard.setString(permalink);
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') });
|
||||
} catch {
|
||||
// Do nothing
|
||||
logEvent(events.ROOM_MSG_ACTION_PERMALINK_F);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = async(message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_COPY);
|
||||
await Clipboard.setString(message.msg);
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||
};
|
||||
|
||||
const handleShare = async(message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_SHARE);
|
||||
try {
|
||||
const permalink = await getPermalink(message);
|
||||
Share.share({ message: permalink });
|
||||
} catch {
|
||||
// Do nothing
|
||||
logEvent(events.ROOM_MSG_ACTION_SHARE_F);
|
||||
}
|
||||
};
|
||||
|
||||
const handleQuote = message => replyInit(message, false);
|
||||
const handleQuote = (message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_QUOTE);
|
||||
replyInit(message, false);
|
||||
};
|
||||
|
||||
const handleStar = async(message) => {
|
||||
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
|
||||
try {
|
||||
await RocketChat.toggleStarMessage(message.id, message.starred);
|
||||
EventEmitter.emit(LISTENER, { message: message.starred ? I18n.t('Message_unstarred') : I18n.t('Message_starred') });
|
||||
} catch (e) {
|
||||
logEvent(events.ROOM_MSG_ACTION_STAR_F);
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePin = async(message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_PIN);
|
||||
try {
|
||||
await RocketChat.togglePinMessage(message.id, message.pinned);
|
||||
} catch (e) {
|
||||
logEvent(events.ROOM_MSG_ACTION_PIN_F);
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReaction = (shortname, message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_REACTION);
|
||||
if (shortname) {
|
||||
onReactionPress(shortname, message.id);
|
||||
} else {
|
||||
|
@ -201,7 +222,13 @@ const MessageActions = React.memo(forwardRef(({
|
|||
hideActionSheet();
|
||||
};
|
||||
|
||||
const handleReadReceipt = message => Navigation.navigate('ReadReceiptsView', { messageId: message.id });
|
||||
const handleReadReceipt = (message) => {
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('ModalStackNavigator', { screen: 'ReadReceiptsView', params: { messageId: message.id } });
|
||||
} else {
|
||||
Navigation.navigate('ReadReceiptsView', { messageId: message.id });
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleTranslation = async(message) => {
|
||||
try {
|
||||
|
@ -228,10 +255,12 @@ const MessageActions = React.memo(forwardRef(({
|
|||
};
|
||||
|
||||
const handleReport = async(message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_REPORT);
|
||||
try {
|
||||
await RocketChat.reportMessage(message.id);
|
||||
Alert.alert(I18n.t('Message_Reported'));
|
||||
} catch (e) {
|
||||
logEvent(events.ROOM_MSG_ACTION_REPORT_F);
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
@ -242,8 +271,10 @@ const MessageActions = React.memo(forwardRef(({
|
|||
callToAction: I18n.t('Delete'),
|
||||
onPress: async() => {
|
||||
try {
|
||||
logEvent(events.ROOM_MSG_ACTION_DELETE);
|
||||
await RocketChat.deleteMessage(message.id, message.subscription.id);
|
||||
} catch (e) {
|
||||
logEvent(events.ROOM_MSG_ACTION_DELETE_F);
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
|
@ -290,7 +321,7 @@ const MessageActions = React.memo(forwardRef(({
|
|||
// Create Discussion
|
||||
options.push({
|
||||
title: I18n.t('Start_a_Discussion'),
|
||||
icon: 'chat',
|
||||
icon: 'discussions',
|
||||
onPress: () => handleCreateDiscussion(message)
|
||||
});
|
||||
|
||||
|
@ -365,7 +396,7 @@ const MessageActions = React.memo(forwardRef(({
|
|||
if (allowDelete(message)) {
|
||||
options.push({
|
||||
title: I18n.t('Delete'),
|
||||
icon: 'trash',
|
||||
icon: 'delete',
|
||||
danger: true,
|
||||
onPress: () => handleDelete(message)
|
||||
});
|
||||
|
@ -375,6 +406,7 @@ const MessageActions = React.memo(forwardRef(({
|
|||
};
|
||||
|
||||
const showMessageActions = async(message) => {
|
||||
logEvent(events.ROOM_SHOW_MSG_ACTIONS);
|
||||
await getPermissions();
|
||||
showActionSheet({
|
||||
options: getOptions(message),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useContext, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
|
||||
import styles from '../styles';
|
||||
import { CustomIcon } from '../../../lib/Icons';
|
||||
|
@ -32,7 +32,7 @@ const Item = ({ item, theme }) => {
|
|||
{ loading ? <ActivityIndicator theme={theme} /> : null }
|
||||
</FastImage>
|
||||
)
|
||||
: <CustomIcon name='clip' size={36} color={themes[theme].actionTintColor} />
|
||||
: <CustomIcon name='attach' size={36} color={themes[theme].actionTintColor} />
|
||||
}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
|
|
@ -10,6 +10,7 @@ import styles from './styles';
|
|||
import I18n from '../../i18n';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { CustomIcon } from '../../lib/Icons';
|
||||
import { logEvent, events } from '../../utils/log';
|
||||
|
||||
const RECORDING_EXTENSION = '.aac';
|
||||
const RECORDING_SETTINGS = {
|
||||
|
@ -103,6 +104,7 @@ export default class RecordAudio extends React.PureComponent {
|
|||
}
|
||||
|
||||
startRecordingAudio = async() => {
|
||||
logEvent(events.ROOM_AUDIO_RECORD);
|
||||
if (!this.isRecorderBusy) {
|
||||
this.isRecorderBusy = true;
|
||||
try {
|
||||
|
@ -120,13 +122,14 @@ export default class RecordAudio extends React.PureComponent {
|
|||
await Audio.requestPermissionsAsync();
|
||||
}
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
logEvent(events.ROOM_AUDIO_RECORD_F);
|
||||
}
|
||||
this.isRecorderBusy = false;
|
||||
}
|
||||
};
|
||||
|
||||
finishRecordingAudio = async() => {
|
||||
logEvent(events.ROOM_AUDIO_FINISH);
|
||||
if (!this.isRecorderBusy) {
|
||||
const { onFinish } = this.props;
|
||||
|
||||
|
@ -147,7 +150,7 @@ export default class RecordAudio extends React.PureComponent {
|
|||
|
||||
onFinish(fileInfo);
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
logEvent(events.ROOM_AUDIO_FINISH_F);
|
||||
}
|
||||
this.setState({ isRecording: false, recordingDurationMillis: 0 });
|
||||
deactivateKeepAwake();
|
||||
|
@ -156,12 +159,13 @@ export default class RecordAudio extends React.PureComponent {
|
|||
};
|
||||
|
||||
cancelRecordingAudio = async() => {
|
||||
logEvent(events.ROOM_AUDIO_CANCEL);
|
||||
if (!this.isRecorderBusy) {
|
||||
this.isRecorderBusy = true;
|
||||
try {
|
||||
await this.recording.stopAndUnloadAsync();
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
logEvent(events.ROOM_AUDIO_CANCEL_F);
|
||||
}
|
||||
this.setState({ isRecording: false, recordingDurationMillis: 0 });
|
||||
deactivateKeepAwake();
|
||||
|
@ -182,7 +186,7 @@ export default class RecordAudio extends React.PureComponent {
|
|||
accessibilityLabel={I18n.t('Send_audio_message')}
|
||||
accessibilityTraits='button'
|
||||
>
|
||||
<CustomIcon name='mic' size={23} color={themes[theme].tintColor} />
|
||||
<CustomIcon name='microphone' size={23} color={themes[theme].tintColor} />
|
||||
</BorderlessButton>
|
||||
);
|
||||
}
|
||||
|
@ -199,7 +203,7 @@ export default class RecordAudio extends React.PureComponent {
|
|||
<CustomIcon
|
||||
size={22}
|
||||
color={themes[theme].dangerColor}
|
||||
name='Cross'
|
||||
name='close'
|
||||
/>
|
||||
</BorderlessButton>
|
||||
<Text
|
||||
|
|
|
@ -72,7 +72,7 @@ const ReplyPreview = React.memo(({
|
|||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
<CustomIcon name='Cross' color={themes[theme].auxiliaryText} size={20} style={styles.close} onPress={close} />
|
||||
<CustomIcon name='close' color={themes[theme].auxiliaryText} size={20} style={styles.close} onPress={close} />
|
||||
</View>
|
||||
);
|
||||
}, (prevProps, nextProps) => prevProps.replying === nextProps.replying && prevProps.theme === nextProps.theme && isEqual(prevProps.message, nextProps.message));
|
||||
|
|
|
@ -8,7 +8,7 @@ const ActionsButton = React.memo(({ theme, onPress }) => (
|
|||
onPress={onPress}
|
||||
testID='messagebox-actions'
|
||||
accessibilityLabel='Message_actions'
|
||||
icon='plus'
|
||||
icon='add'
|
||||
theme={theme}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -8,7 +8,7 @@ const CancelEditingButton = React.memo(({ theme, onPress }) => (
|
|||
onPress={onPress}
|
||||
testID='messagebox-cancel-editing'
|
||||
accessibilityLabel='Cancel_editing'
|
||||
icon='Cross'
|
||||
icon='close'
|
||||
theme={theme}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -8,7 +8,7 @@ const SendButton = React.memo(({ theme, onPress }) => (
|
|||
onPress={onPress}
|
||||
testID='messagebox-send-message'
|
||||
accessibilityLabel='Send_message'
|
||||
icon='send-active'
|
||||
icon='send-filled'
|
||||
theme={theme}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -17,8 +17,8 @@ import RocketChat from '../../lib/rocketchat';
|
|||
import styles from './styles';
|
||||
import database from '../../lib/database';
|
||||
import { emojis } from '../../emojis';
|
||||
import log, { logEvent, events } from '../../utils/log';
|
||||
import RecordAudio from './RecordAudio';
|
||||
import log from '../../utils/log';
|
||||
import I18n from '../../i18n';
|
||||
import ReplyPreview from './ReplyPreview';
|
||||
import debounce from '../../utils/debounce';
|
||||
|
@ -122,6 +122,7 @@ class MessageBox extends Component {
|
|||
command: {}
|
||||
};
|
||||
this.text = '';
|
||||
this.selection = { start: 0, end: 0 };
|
||||
this.focused = false;
|
||||
|
||||
// MessageBox Actions
|
||||
|
@ -133,7 +134,7 @@ class MessageBox extends Component {
|
|||
},
|
||||
{
|
||||
title: I18n.t('Take_a_video'),
|
||||
icon: 'video-1',
|
||||
icon: 'camera',
|
||||
onPress: this.takeVideo
|
||||
},
|
||||
{
|
||||
|
@ -143,12 +144,12 @@ class MessageBox extends Component {
|
|||
},
|
||||
{
|
||||
title: I18n.t('Choose_file'),
|
||||
icon: 'folder',
|
||||
icon: 'attach',
|
||||
onPress: this.chooseFile
|
||||
},
|
||||
{
|
||||
title: I18n.t('Create_Discussion'),
|
||||
icon: 'chat',
|
||||
icon: 'discussions',
|
||||
onPress: this.createDiscussion
|
||||
}
|
||||
];
|
||||
|
@ -331,6 +332,10 @@ class MessageBox extends Component {
|
|||
this.setInput(text);
|
||||
}
|
||||
|
||||
onSelectionChange = (e) => {
|
||||
this.selection = e.nativeEvent.selection;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
debouncedOnChangeText = debounce(async(text) => {
|
||||
const { sharing } = this.props;
|
||||
|
@ -358,9 +363,9 @@ class MessageBox extends Component {
|
|||
|
||||
if (!isTextEmpty) {
|
||||
try {
|
||||
const { start, end } = this.component?.lastNativeSelection;
|
||||
const { start, end } = this.selection;
|
||||
const cursor = Math.max(start, end);
|
||||
const lastNativeText = this.component?.lastNativeText || '';
|
||||
const lastNativeText = this.text;
|
||||
// matches if text either starts with '/' or have (@,#,:) then it groups whatever comes next of mention type
|
||||
let regexp = /(#|@|:|^\/)([a-z0-9._-]+)$/im;
|
||||
|
||||
|
@ -399,7 +404,7 @@ class MessageBox extends Component {
|
|||
}
|
||||
const { trackingType } = this.state;
|
||||
const msg = this.text;
|
||||
const { start, end } = this.component?.lastNativeSelection;
|
||||
const { start, end } = this.selection;
|
||||
const cursor = Math.max(start, end);
|
||||
const regexp = /([a-z0-9._-]+)$/im;
|
||||
const result = msg.substr(0, cursor).replace(regexp, '');
|
||||
|
@ -410,7 +415,8 @@ class MessageBox extends Component {
|
|||
if ((trackingType === MENTIONS_TRACKING_TYPE_COMMANDS) && item.providesPreview) {
|
||||
this.setState({ showCommandPreview: true });
|
||||
}
|
||||
this.setInput(text);
|
||||
const newCursor = cursor + mentionName.length;
|
||||
this.setInput(text, { start: newCursor, end: newCursor });
|
||||
this.focus();
|
||||
requestAnimationFrame(() => this.stopTrackingMention());
|
||||
}
|
||||
|
@ -443,15 +449,11 @@ class MessageBox extends Component {
|
|||
let newText = '';
|
||||
|
||||
// if messagebox has an active cursor
|
||||
if (this.component?.lastNativeSelection) {
|
||||
const { start, end } = this.component.lastNativeSelection;
|
||||
const cursor = Math.max(start, end);
|
||||
newText = `${ text.substr(0, cursor) }${ emoji }${ text.substr(cursor) }`;
|
||||
} else {
|
||||
// if messagebox doesn't have a cursor, just append selected emoji
|
||||
newText = `${ text }${ emoji }`;
|
||||
}
|
||||
this.setInput(newText);
|
||||
const { start, end } = this.selection;
|
||||
const cursor = Math.max(start, end);
|
||||
newText = `${ text.substr(0, cursor) }${ emoji }${ text.substr(cursor) }`;
|
||||
const newCursor = cursor + emoji.length;
|
||||
this.setInput(newText, { start: newCursor, end: newCursor });
|
||||
this.setShowSend(true);
|
||||
}
|
||||
|
||||
|
@ -551,11 +553,12 @@ class MessageBox extends Component {
|
|||
this.setState({ commandPreview: [], showCommandPreview: true, command: {} });
|
||||
}
|
||||
|
||||
setInput = (text) => {
|
||||
setInput = (text, selection) => {
|
||||
this.text = text;
|
||||
if (this.component && this.component.setNativeProps) {
|
||||
this.component.setNativeProps({ text });
|
||||
if (selection) {
|
||||
return this.component.setTextAndSelection(text, selection);
|
||||
}
|
||||
this.component.setNativeProps({ text });
|
||||
}
|
||||
|
||||
setShowSend = (showSend) => {
|
||||
|
@ -582,37 +585,41 @@ class MessageBox extends Component {
|
|||
}
|
||||
|
||||
takePhoto = async() => {
|
||||
logEvent(events.ROOM_BOX_ACTION_PHOTO);
|
||||
try {
|
||||
const image = await ImagePicker.openCamera(this.imagePickerConfig);
|
||||
if (this.canUploadFile(image)) {
|
||||
this.openShareView([image]);
|
||||
}
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
logEvent(events.ROOM_BOX_ACTION_PHOTO_F);
|
||||
}
|
||||
}
|
||||
|
||||
takeVideo = async() => {
|
||||
logEvent(events.ROOM_BOX_ACTION_VIDEO);
|
||||
try {
|
||||
const video = await ImagePicker.openCamera(this.videoPickerConfig);
|
||||
if (this.canUploadFile(video)) {
|
||||
this.openShareView([video]);
|
||||
}
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
logEvent(events.ROOM_BOX_ACTION_VIDEO_F);
|
||||
}
|
||||
}
|
||||
|
||||
chooseFromLibrary = async() => {
|
||||
logEvent(events.ROOM_BOX_ACTION_LIBRARY);
|
||||
try {
|
||||
const attachments = await ImagePicker.openPicker(this.libraryPickerConfig);
|
||||
this.openShareView(attachments);
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
logEvent(events.ROOM_BOX_ACTION_LIBRARY_F);
|
||||
}
|
||||
}
|
||||
|
||||
chooseFile = async() => {
|
||||
logEvent(events.ROOM_BOX_ACTION_FILE);
|
||||
try {
|
||||
const res = await DocumentPicker.pick({
|
||||
type: [DocumentPicker.types.allFiles]
|
||||
|
@ -628,6 +635,7 @@ class MessageBox extends Component {
|
|||
}
|
||||
} catch (e) {
|
||||
if (!DocumentPicker.isCancel(e)) {
|
||||
logEvent(events.ROOM_BOX_ACTION_FILE_F);
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
|
@ -645,6 +653,7 @@ class MessageBox extends Component {
|
|||
}
|
||||
|
||||
createDiscussion = () => {
|
||||
logEvent(events.ROOM_BOX_ACTION_DISCUSSION);
|
||||
const { isMasterDetail } = this.props;
|
||||
const params = { channel: this.room, showCloseModal: true };
|
||||
if (isMasterDetail) {
|
||||
|
@ -655,6 +664,7 @@ class MessageBox extends Component {
|
|||
}
|
||||
|
||||
showMessageBoxActions = () => {
|
||||
logEvent(events.ROOM_SHOW_BOX_ACTIONS);
|
||||
const { showActionSheet } = this.props;
|
||||
showActionSheet({ options: this.options });
|
||||
}
|
||||
|
@ -665,10 +675,9 @@ class MessageBox extends Component {
|
|||
this.clearInput();
|
||||
}
|
||||
|
||||
openEmoji = async() => {
|
||||
await this.setState({
|
||||
showEmojiKeyboard: true
|
||||
});
|
||||
openEmoji = () => {
|
||||
logEvent(events.ROOM_OPEN_EMOJI);
|
||||
this.setState({ showEmojiKeyboard: true });
|
||||
}
|
||||
|
||||
recordingCallback = (recording) => {
|
||||
|
@ -729,6 +738,7 @@ class MessageBox extends Component {
|
|||
Q.where('id', Q.like(`${ Q.sanitizeLikeString(command) }%`))
|
||||
).fetch();
|
||||
if (slashCommand.length > 0) {
|
||||
logEvent(events.COMMAND_RUN);
|
||||
try {
|
||||
const messageWithoutCommand = message.replace(/([^\s]+)/, '').trim();
|
||||
const [{ appId }] = slashCommand;
|
||||
|
@ -736,6 +746,7 @@ class MessageBox extends Component {
|
|||
RocketChat.runSlashCommand(command, roomId, messageWithoutCommand, triggerId, tmid || messageTmid);
|
||||
replyCancel();
|
||||
} catch (e) {
|
||||
logEvent(events.COMMAND_RUN_F);
|
||||
log(e);
|
||||
}
|
||||
this.clearInput();
|
||||
|
@ -888,6 +899,7 @@ class MessageBox extends Component {
|
|||
blurOnSubmit={false}
|
||||
placeholder={I18n.t('New_Message')}
|
||||
onChangeText={this.onChangeText}
|
||||
onSelectionChange={this.onSelectionChange}
|
||||
underlineColorAndroid='transparent'
|
||||
defaultValue=''
|
||||
multiline
|
||||
|
|
|
@ -81,7 +81,7 @@ const MessageErrorActions = forwardRef(({ tmid }, ref) => {
|
|||
},
|
||||
{
|
||||
title: I18n.t('Delete'),
|
||||
icon: 'trash',
|
||||
icon: 'delete',
|
||||
danger: true,
|
||||
onPress: () => handleDelete(message)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { CustomIcon } from '../../../lib/Icons';
|
|||
const LockIcon = React.memo(({ theme }) => (
|
||||
<Row style={styles.row}>
|
||||
<View style={styles.iconView}>
|
||||
<CustomIcon name='lock' size={40} color={themes[theme].passcodeLockIcon} />
|
||||
<CustomIcon name='auth' size={40} color={themes[theme].passcodeLockIcon} />
|
||||
</View>
|
||||
</Row>
|
||||
));
|
||||
|
|
|
@ -104,7 +104,7 @@ const ModalContent = React.memo(({
|
|||
<View style={styles.titleContainer}>
|
||||
<CustomIcon
|
||||
style={[styles.closeButton, { color: themes[props.theme].buttonText }]}
|
||||
name='Cross'
|
||||
name='close'
|
||||
size={20}
|
||||
/>
|
||||
<Text style={[styles.title, { color: themes[props.theme].buttonText }]}>{I18n.t('Reactions')}</Text>
|
||||
|
|
|
@ -20,19 +20,19 @@ const RoomTypeIcon = React.memo(({
|
|||
|
||||
const color = themes[theme].auxiliaryText;
|
||||
|
||||
let icon = 'lock';
|
||||
let icon = 'channel-private';
|
||||
if (type === 'discussion') {
|
||||
icon = 'chat';
|
||||
icon = 'discussions';
|
||||
} else if (type === 'c') {
|
||||
icon = 'hash';
|
||||
icon = 'channel-public';
|
||||
} else if (type === 'd') {
|
||||
if (isGroupChat) {
|
||||
icon = 'team';
|
||||
} else {
|
||||
icon = 'at';
|
||||
icon = 'mention';
|
||||
}
|
||||
} else if (type === 'l') {
|
||||
icon = 'livechat';
|
||||
icon = 'omnichannel';
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -61,7 +61,7 @@ const SearchBox = ({
|
|||
]}
|
||||
>
|
||||
<View style={[styles.searchBox, { backgroundColor: themes[theme].searchboxBackground }]}>
|
||||
<CustomIcon name='magnifier' size={14} color={themes[theme].auxiliaryText} />
|
||||
<CustomIcon name='search' size={14} color={themes[theme].auxiliaryText} />
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
autoCapitalize='none'
|
||||
|
|
|
@ -14,7 +14,7 @@ const Status = React.memo(({
|
|||
borderRadius: size,
|
||||
width: size,
|
||||
height: size,
|
||||
backgroundColor: STATUS_COLORS[status],
|
||||
backgroundColor: STATUS_COLORS[status] ?? STATUS_COLORS.offline,
|
||||
borderColor: themes[theme].backgroundColor
|
||||
}
|
||||
]}
|
||||
|
|
|
@ -111,7 +111,7 @@ export default class RCTextInput extends React.PureComponent {
|
|||
return (
|
||||
<BorderlessButton onPress={this.tooglePassword} style={[styles.iconContainer, styles.iconRight]}>
|
||||
<CustomIcon
|
||||
name={showPassword ? 'eye' : 'eye-off'}
|
||||
name={showPassword ? 'unread-on-top' : 'unread-on-top-disabled'}
|
||||
testID={testID ? `${ testID }-icon-right` : null}
|
||||
style={{ color: themes[theme].auxiliaryText }}
|
||||
size={20}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { View, Text, InteractionManager } from 'react-native';
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { sha256 } from 'js-sha256';
|
||||
|
@ -99,6 +99,7 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }) => {
|
|||
<TextInput
|
||||
value={code}
|
||||
theme={theme}
|
||||
inputRef={e => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())}
|
||||
returnKeyType='send'
|
||||
autoCapitalize='none'
|
||||
onChangeText={setCode}
|
||||
|
|
|
@ -14,9 +14,10 @@ export default StyleSheet.create({
|
|||
borderRadius: 4
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
fontSize: 16,
|
||||
paddingBottom: 8,
|
||||
...sharedStyles.textBold
|
||||
...sharedStyles.textBold,
|
||||
...sharedStyles.textAlignCenter
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 14,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
import PropTypes from 'prop-types';
|
||||
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { Text, View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { textParser } from '../utils';
|
||||
|
@ -24,7 +24,7 @@ const Chip = ({
|
|||
<>
|
||||
{item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null}
|
||||
<Text numberOfLines={1} style={[styles.chipText, { color: themes[theme].titleText }]}>{textParser([item.text])}</Text>
|
||||
<CustomIcon name='Cross' size={16} color={themes[theme].auxiliaryText} />
|
||||
<CustomIcon name='close' size={16} color={themes[theme].auxiliaryText} />
|
||||
</>
|
||||
</Touchable>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { Text, FlatList } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
|
||||
import Separator from '../../Separator';
|
||||
import Check from '../../Check';
|
||||
|
|
|
@ -82,7 +82,7 @@ export const Overflow = ({
|
|||
hitSlop={BUTTON_HIT_SLOP}
|
||||
style={styles.menu}
|
||||
>
|
||||
{!loading ? <CustomIcon size={18} name='menu' color={themes[theme].bodyText} /> : <ActivityIndicator style={styles.loading} theme={theme} />}
|
||||
{!loading ? <CustomIcon size={18} name='kebab' color={themes[theme].bodyText} /> : <ActivityIndicator style={styles.loading} theme={theme} />}
|
||||
</Touchable>
|
||||
<Popover
|
||||
isVisible={show}
|
||||
|
|
|
@ -5,30 +5,44 @@ import { Text } from 'react-native';
|
|||
import { themes } from '../../constants/colors';
|
||||
|
||||
import styles from './styles';
|
||||
import { logEvent, events } from '../../utils/log';
|
||||
|
||||
const AtMention = React.memo(({
|
||||
mention, mentions, username, navToRoomInfo, style = [], useRealName, theme
|
||||
}) => {
|
||||
let mentionStyle = { ...styles.mention, color: themes[theme].buttonText };
|
||||
if (mention === 'all' || mention === 'here') {
|
||||
return <Text style={[mentionStyle, styles.mentionAll, ...style]}>{mention}</Text>;
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
styles.mention,
|
||||
{
|
||||
color: themes[theme].mentionGroupColor,
|
||||
backgroundColor: themes[theme].mentionGroupBackground
|
||||
},
|
||||
...style
|
||||
]}
|
||||
>{mention}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
let mentionStyle = {};
|
||||
if (mention === username) {
|
||||
mentionStyle = {
|
||||
...mentionStyle,
|
||||
backgroundColor: themes[theme].actionTintColor
|
||||
color: themes[theme].mentionMeColor,
|
||||
backgroundColor: themes[theme].mentionMeBackground
|
||||
};
|
||||
} else {
|
||||
mentionStyle = {
|
||||
...mentionStyle,
|
||||
color: themes[theme].actionTintColor
|
||||
color: themes[theme].mentionOtherColor,
|
||||
backgroundColor: themes[theme].mentionOtherBackground
|
||||
};
|
||||
}
|
||||
|
||||
const user = mentions && mentions.length && mentions.find(m => m.username === mention);
|
||||
const user = mentions?.find?.(m => m && m.username === mention);
|
||||
|
||||
const handlePress = () => {
|
||||
logEvent(events.ROOM_MENTION_GO_USER_INFO);
|
||||
const navParam = {
|
||||
t: 'd',
|
||||
rid: user && user._id
|
||||
|
@ -39,7 +53,7 @@ const AtMention = React.memo(({
|
|||
if (user) {
|
||||
return (
|
||||
<Text
|
||||
style={[mentionStyle, ...style]}
|
||||
style={[styles.mention, mentionStyle, ...style]}
|
||||
onPress={handlePress}
|
||||
>
|
||||
{useRealName && user.name ? user.name : user.username}
|
||||
|
|
|
@ -21,10 +21,16 @@ const Hashtag = React.memo(({
|
|||
if (channels && channels.length && channels.findIndex(channel => channel.name === hashtag) !== -1) {
|
||||
return (
|
||||
<Text
|
||||
style={[styles.mention, ...style]}
|
||||
style={[
|
||||
styles.mention,
|
||||
{
|
||||
color: themes[theme].mentionOtherColor,
|
||||
backgroundColor: themes[theme].mentionOtherBackground
|
||||
},
|
||||
...style]}
|
||||
onPress={handlePress}
|
||||
>
|
||||
{hashtag}
|
||||
{`#${ hashtag }`}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -383,9 +383,9 @@ class Markdown extends PureComponent {
|
|||
m = m.replace(/^\[([\s]]*)\]\(([^)]*)\)\s/, '').trim();
|
||||
|
||||
if (preview) {
|
||||
m = m.replace(/\n+/g, ' ');
|
||||
m = shortnameToUnicode(m);
|
||||
m = removeMarkdown(m);
|
||||
m = m.replace(/\n+/g, ' ');
|
||||
return (
|
||||
<Text accessibilityLabel={m} style={[styles.text, { color: themes[theme].bodyText }, ...style]} numberOfLines={numberOfLines} testID={testID}>
|
||||
{m}
|
||||
|
|
|
@ -54,13 +54,9 @@ export default StyleSheet.create({
|
|||
temp: { opacity: 0.3 },
|
||||
mention: {
|
||||
fontSize: 16,
|
||||
color: '#0072FE',
|
||||
padding: 5,
|
||||
...sharedStyles.textMedium,
|
||||
backgroundColor: '#E8F2FF'
|
||||
},
|
||||
mentionAll: {
|
||||
backgroundColor: '#FF5B5A'
|
||||
letterSpacing: 0.5
|
||||
},
|
||||
paragraph: {
|
||||
marginTop: 0,
|
||||
|
|
|
@ -79,7 +79,7 @@ const Button = React.memo(({
|
|||
{
|
||||
loading
|
||||
? <ActivityIndicator style={[styles.playPauseButton, styles.audioLoading]} theme={theme} />
|
||||
: <CustomIcon name={paused ? 'play' : 'pause'} size={36} color={themes[theme].tintColor} />
|
||||
: <CustomIcon name={paused ? 'play-filled' : 'pause-filled'} size={36} color={themes[theme].tintColor} />
|
||||
}
|
||||
</Touchable>
|
||||
));
|
||||
|
|
|
@ -26,7 +26,7 @@ const Broadcast = React.memo(({
|
|||
testID='message-broadcast-reply'
|
||||
>
|
||||
<>
|
||||
<CustomIcon name='back' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
|
||||
<CustomIcon name='arrow-back' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
|
||||
<Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Reply')}</Text>
|
||||
</>
|
||||
</Touchable>
|
||||
|
|
|
@ -22,7 +22,7 @@ const CallButton = React.memo(({
|
|||
hitSlop={BUTTON_HIT_SLOP}
|
||||
>
|
||||
<>
|
||||
<CustomIcon name='video-1' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
|
||||
<CustomIcon name='camera' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
|
||||
<Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Click_to_join')}</Text>
|
||||
</>
|
||||
</Touchable>
|
||||
|
|
|
@ -29,7 +29,7 @@ const Discussion = React.memo(({
|
|||
hitSlop={BUTTON_HIT_SLOP}
|
||||
>
|
||||
<>
|
||||
<CustomIcon name='chat' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
|
||||
<CustomIcon name='discussions' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
|
||||
<Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{buttonText}</Text>
|
||||
</>
|
||||
</Touchable>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
import equal from 'deep-equal';
|
||||
import { createImageProgress } from 'react-native-image-progress';
|
||||
import * as Progress from 'react-native-progress';
|
||||
|
|
|
@ -23,7 +23,7 @@ const AddReaction = React.memo(({ theme }) => {
|
|||
hitSlop={BUTTON_HIT_SLOP}
|
||||
>
|
||||
<View style={[styles.reactionContainer, { borderColor: themes[theme].borderColor }]}>
|
||||
<CustomIcon name='add-reaction' size={21} color={themes[theme].tintColor} />
|
||||
<CustomIcon name='reaction-add' size={21} color={themes[theme].tintColor} />
|
||||
</View>
|
||||
</Touchable>
|
||||
);
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
View, Text, StyleSheet, Clipboard
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import Touchable from './Touchable';
|
||||
|
|
|
@ -49,7 +49,7 @@ const Video = React.memo(({
|
|||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||
>
|
||||
<CustomIcon
|
||||
name='play'
|
||||
name='play-filled'
|
||||
size={54}
|
||||
color={themes[theme].buttonText}
|
||||
/>
|
||||
|
|
|
@ -483,6 +483,7 @@ export default {
|
|||
Tags: 'Tags',
|
||||
Take_a_photo: 'Take a photo',
|
||||
Take_a_video: 'Take a video',
|
||||
Take_it: 'Take it!',
|
||||
tap_to_change_status: 'tap to change status',
|
||||
Tap_to_view_servers_list: 'Tap to view servers list',
|
||||
Terms_of_Service: ' Terms of Service ',
|
||||
|
@ -621,5 +622,7 @@ export default {
|
|||
Passcode_app_locked_title: 'App locked',
|
||||
Passcode_app_locked_subtitle: 'Try again in {{timeLeft}} seconds',
|
||||
After_seconds_set_by_admin: 'After {{seconds}} seconds (set by admin)',
|
||||
Dont_activate: 'Don\'t activate now'
|
||||
Dont_activate: 'Don\'t activate now',
|
||||
Queued_chats: 'Queued chats',
|
||||
Queue_is_empty: 'Queue is empty'
|
||||
};
|
||||
|
|
|
@ -108,8 +108,10 @@ export default {
|
|||
are_typing: 'estão digitando',
|
||||
Are_you_sure_question_mark: 'Você tem certeza?',
|
||||
Are_you_sure_you_want_to_leave_the_room: 'Tem certeza de que deseja sair da sala {{room}}?',
|
||||
Audio: 'Áudio',
|
||||
Authenticating: 'Autenticando',
|
||||
Automatic: 'Automático',
|
||||
Auto_Translate: 'Tradução automática',
|
||||
Avatar_changed_successfully: 'Avatar alterado com sucesso!',
|
||||
Avatar_Url: 'Avatar URL',
|
||||
Away: 'Ausente',
|
||||
|
@ -172,6 +174,7 @@ export default {
|
|||
DELETE: 'EXCLUIR',
|
||||
deleting_room: 'excluindo sala',
|
||||
Direct_Messages: 'Mensagens Diretas',
|
||||
DESKTOP_OPTIONS: 'OPÇÕES DE ÁREA DE TRABALHO',
|
||||
Directory: 'Diretório',
|
||||
description: 'descrição',
|
||||
Description: 'Descrição',
|
||||
|
@ -192,6 +195,7 @@ export default {
|
|||
Email: 'Email',
|
||||
email: 'e-mail',
|
||||
Empty_title: 'Título vazio',
|
||||
Enable_Auto_Translate: 'Ativar a tradução automática',
|
||||
Enable_notifications: 'Habilitar notificações',
|
||||
Everyone_can_access_this_channel: 'Todos podem acessar este canal',
|
||||
Error_uploading: 'Erro subindo',
|
||||
|
@ -234,6 +238,7 @@ export default {
|
|||
Message_HideType_room_unarchived: 'Sala desarquivada',
|
||||
IP: 'IP',
|
||||
In_app: 'No app',
|
||||
In_App_and_Desktop_Alert_info: 'Exibe um banner na parte superior da tela quando o aplicativo é aberto e exibe uma notificação na área de trabalho',
|
||||
Invisible: 'Invisível',
|
||||
Invite: 'Convidar',
|
||||
is_typing: 'está digitando',
|
||||
|
@ -299,6 +304,9 @@ export default {
|
|||
Nothing_to_save: 'Nada para salvar!',
|
||||
Notify_active_in_this_room: 'Notificar usuários ativos nesta sala',
|
||||
Notify_all_in_this_room: 'Notificar todos nesta sala',
|
||||
Notifications: 'Notificações',
|
||||
Notification_Duration: 'Duração da notificação',
|
||||
Notification_Preferences: 'Preferências de notificação',
|
||||
Not_RC_Server: 'Este não é um servidor Rocket.Chat.\n{{contact}}',
|
||||
No_available_agents_to_transfer: 'Nenhum agente disponível para transferência',
|
||||
Offline: 'Offline',
|
||||
|
@ -340,6 +348,7 @@ export default {
|
|||
Profile: 'Perfil',
|
||||
Public_Channel: 'Canal Público',
|
||||
Public: 'Público',
|
||||
Push_Notifications_Alert_Info: 'Essas notificações são entregues a você quando o aplicativo não está aberto',
|
||||
Quote: 'Citar',
|
||||
Reactions_are_disabled: 'Reagir está desabilitado',
|
||||
Reactions_are_enabled: 'Reagir está habilitado',
|
||||
|
@ -348,6 +357,8 @@ export default {
|
|||
Read_External_Permission: 'Permissão de acesso à arquivos',
|
||||
Read_Only_Channel: 'Canal Somente Leitura',
|
||||
Read_Only: 'Somente Leitura',
|
||||
Receive_Group_Mentions: 'Receber menções de grupo',
|
||||
Receive_Group_Mentions_Info: 'Receber menções @all e @here',
|
||||
Register: 'Registrar',
|
||||
Read_Receipt: 'Lida por',
|
||||
Repeat_Password: 'Repetir Senha',
|
||||
|
@ -355,6 +366,8 @@ export default {
|
|||
replies: 'respostas',
|
||||
reply: 'resposta',
|
||||
Reply: 'Responder',
|
||||
Receive_Notification: 'Receber Notificação',
|
||||
Receive_notifications_from: 'Receber notificação de {{name}}',
|
||||
Resend: 'Reenviar',
|
||||
Reset_password: 'Resetar senha',
|
||||
resetting_password: 'redefinindo senha',
|
||||
|
@ -411,10 +424,13 @@ export default {
|
|||
Share: 'Compartilhar',
|
||||
Share_Link: 'Share Link',
|
||||
Show_more: 'Mostrar mais..',
|
||||
Show_Unread_Counter: 'Mostrar contador não lido',
|
||||
Show_Unread_Counter_Info: 'O contador não lido é exibido como um emblema à direita do canal, na lista',
|
||||
Sign_in_your_server: 'Entrar no seu servidor',
|
||||
Sign_Up: 'Registrar',
|
||||
Some_field_is_invalid_or_empty: 'Algum campo está inválido ou vazio',
|
||||
Sorting_by: 'Ordenando por {{key}}',
|
||||
Sound: 'Som da notificação',
|
||||
Star_room: 'Favoritar sala',
|
||||
Star: 'Favorito',
|
||||
Starred_Messages: 'Mensagens Favoritas',
|
||||
|
@ -551,5 +567,7 @@ export default {
|
|||
Passcode_app_locked_title: 'Aplicativo bloqueado',
|
||||
Passcode_app_locked_subtitle: 'Tente novamente em {{timeLeft}} segundos',
|
||||
After_seconds_set_by_admin: 'Após {{seconds}} segundos (Configurado pelo adm)',
|
||||
Dont_activate: 'Não ativar agora'
|
||||
Dont_activate: 'Não ativar agora',
|
||||
Queued_chats: 'Bate-papos na fila',
|
||||
Queue_is_empty: 'A fila está vazia'
|
||||
};
|
||||
|
|
|
@ -50,6 +50,13 @@ const parseDeepLinking = (url) => {
|
|||
return parseQuery(url);
|
||||
}
|
||||
}
|
||||
const call = /^(https:\/\/)?jitsi.rocket.chat\//;
|
||||
if (url.match(call)) {
|
||||
url = url.replace(call, '').trim();
|
||||
if (url) {
|
||||
return { path: url, isCall: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
|
|
@ -25,4 +25,6 @@ export default class Server extends Model {
|
|||
@field('auto_lock_time') autoLockTime;
|
||||
|
||||
@field('biometry') biometry;
|
||||
|
||||
@field('unique_id') uniqueID;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,8 @@ export default class Subscription extends Model {
|
|||
|
||||
@field('user_mentions') userMentions;
|
||||
|
||||
@field('group_mentions') groupMentions;
|
||||
|
||||
@date('room_updated_at') roomUpdatedAt;
|
||||
|
||||
@field('ro') ro;
|
||||
|
|
|
@ -118,6 +118,17 @@ export default schemaMigrations({
|
|||
]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 9,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: 'subscriptions',
|
||||
columns: [
|
||||
{ name: 'group_mentions', type: 'number', isOptional: true }
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -26,6 +26,17 @@ export default schemaMigrations({
|
|||
]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 5,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: 'servers',
|
||||
columns: [
|
||||
{ name: 'unique_id', type: 'string', isOptional: true }
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||
|
||||
export default appSchema({
|
||||
version: 8,
|
||||
version: 9,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'subscriptions',
|
||||
|
@ -19,6 +19,7 @@ export default appSchema({
|
|||
{ name: 'roles', type: 'string', isOptional: true },
|
||||
{ name: 'unread', type: 'number' },
|
||||
{ name: 'user_mentions', type: 'number' },
|
||||
{ name: 'group_mentions', type: 'number' },
|
||||
{ name: 'room_updated_at', type: 'number' },
|
||||
{ name: 'ro', type: 'boolean' },
|
||||
{ name: 'last_open', type: 'number', isOptional: true },
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||
|
||||
export default appSchema({
|
||||
version: 4,
|
||||
version: 5,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'users',
|
||||
|
@ -28,7 +28,8 @@ export default appSchema({
|
|||
{ name: 'last_local_authenticated_session', type: 'number', isOptional: true },
|
||||
{ name: 'auto_lock', type: 'boolean', isOptional: true },
|
||||
{ name: 'auto_lock_time', type: 'number', isOptional: true },
|
||||
{ name: 'biometry', type: 'boolean', isOptional: true }
|
||||
{ name: 'biometry', type: 'boolean', isOptional: true },
|
||||
{ name: 'unique_id', type: 'string', isOptional: true }
|
||||
]
|
||||
})
|
||||
]
|
||||
|
|
|
@ -1,41 +1,41 @@
|
|||
import reduxStore from '../createStore';
|
||||
import Navigation from '../Navigation';
|
||||
import { logEvent, events } from '../../utils/log';
|
||||
|
||||
async function jitsiURL({ rid }) {
|
||||
const { settings } = reduxStore.getState();
|
||||
const { Jitsi_Enabled } = settings;
|
||||
|
||||
const jitsiBaseUrl = ({
|
||||
Jitsi_Enabled, Jitsi_SSL, Jitsi_Domain, Jitsi_URL_Room_Prefix, uniqueID
|
||||
}) => {
|
||||
if (!Jitsi_Enabled) {
|
||||
return '';
|
||||
}
|
||||
const uniqueIdentifier = uniqueID || 'undefined';
|
||||
const domain = Jitsi_Domain;
|
||||
|
||||
const {
|
||||
Jitsi_Domain, Jitsi_URL_Room_Prefix, Jitsi_SSL, Jitsi_Enabled_TokenAuth, uniqueID
|
||||
} = settings;
|
||||
|
||||
const domain = `${ Jitsi_Domain }/`;
|
||||
const prefix = Jitsi_URL_Room_Prefix;
|
||||
const uniqueIdentifier = uniqueID || 'undefined';
|
||||
const protocol = Jitsi_SSL ? 'https://' : 'http://';
|
||||
|
||||
const urlProtocol = Jitsi_SSL ? 'https://' : 'http://';
|
||||
const urlDomain = `${ domain }/`;
|
||||
|
||||
return `${ urlProtocol }${ urlDomain }${ prefix }${ uniqueIdentifier }`;
|
||||
};
|
||||
|
||||
async function callJitsi(rid, onlyAudio = false) {
|
||||
let accessToken;
|
||||
let queryString = '';
|
||||
const { settings } = reduxStore.getState();
|
||||
const { Jitsi_Enabled_TokenAuth } = settings;
|
||||
|
||||
if (Jitsi_Enabled_TokenAuth) {
|
||||
try {
|
||||
accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', rid);
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
const accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', rid);
|
||||
queryString = `?jwt=${ accessToken }`;
|
||||
} catch {
|
||||
logEvent(events.RA_JITSI_F);
|
||||
}
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
queryString = `?jwt=${ accessToken }`;
|
||||
}
|
||||
return `${ protocol }${ domain }${ prefix }${ uniqueIdentifier }${ rid }${ queryString }`;
|
||||
}
|
||||
|
||||
Navigation.navigate('JitsiMeetView', { url: `${ jitsiBaseUrl(settings) }${ rid }${ queryString }`, onlyAudio, rid });
|
||||
async function callJitsi(rid, onlyAudio = false) {
|
||||
logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
|
||||
const url = await jitsiURL.call(this, { rid });
|
||||
Navigation.navigate('JitsiMeetView', { url, onlyAudio, rid });
|
||||
}
|
||||
|
||||
export default callJitsi;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import database from '../database';
|
||||
import store from '../createStore';
|
||||
|
||||
const restTypes = {
|
||||
channel: 'channels', direct: 'im', group: 'groups'
|
||||
|
@ -53,11 +54,17 @@ async function open({ type, rid, name }) {
|
|||
}
|
||||
}
|
||||
|
||||
export default async function canOpenRoom({ rid, path }) {
|
||||
export default async function canOpenRoom({ rid, path, isCall }) {
|
||||
try {
|
||||
const db = database.active;
|
||||
const subsCollection = db.collections.get('subscriptions');
|
||||
const [type, name] = path.split('/');
|
||||
|
||||
if (isCall && !rid) {
|
||||
// Extract rid from a Jitsi URL
|
||||
// Eg.: [Jitsi_URL_Room_Prefix][uniqueID][rid][?jwt]
|
||||
const { Jitsi_URL_Room_Prefix, uniqueID } = store.getState().settings;
|
||||
rid = path.replace(`${ Jitsi_URL_Room_Prefix }${ uniqueID }`, '').replace(/\?(.*)/g, '');
|
||||
}
|
||||
|
||||
if (rid) {
|
||||
try {
|
||||
|
@ -75,8 +82,10 @@ export default async function canOpenRoom({ rid, path }) {
|
|||
}
|
||||
}
|
||||
|
||||
const [type, name] = path.split('/');
|
||||
try {
|
||||
return await open.call(this, { type, rid, name });
|
||||
const result = await open.call(this, { type, rid, name });
|
||||
return result;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import protectedFunction from './helpers/protectedFunction';
|
|||
import fetch from '../../utils/fetch';
|
||||
import { DEFAULT_AUTO_LOCK } from '../../constants/localAuthentication';
|
||||
|
||||
const serverInfoKeys = ['Site_Name', 'UI_Use_Real_Name', 'FileUpload_MediaTypeWhiteList', 'FileUpload_MaxFileSize', 'Force_Screen_Lock', 'Force_Screen_Lock_After'];
|
||||
const serverInfoKeys = ['Site_Name', 'UI_Use_Real_Name', 'FileUpload_MediaTypeWhiteList', 'FileUpload_MaxFileSize', 'Force_Screen_Lock', 'Force_Screen_Lock_After', 'uniqueID'];
|
||||
|
||||
// these settings are used only on onboarding process
|
||||
const loginSettings = [
|
||||
|
@ -68,6 +68,9 @@ const serverInfoUpdate = async(serverInfo, iconSetting) => {
|
|||
return { ...allSettings, autoLockTime: setting.valueAsNumber };
|
||||
}
|
||||
}
|
||||
if (setting._id === 'uniqueID') {
|
||||
return { ...allSettings, uniqueID: setting.valueAsString };
|
||||
}
|
||||
return allSettings;
|
||||
}, {});
|
||||
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import log from '../../../utils/log';
|
||||
import store from '../../createStore';
|
||||
import RocketChat from '../../rocketchat';
|
||||
import {
|
||||
inquiryRequest,
|
||||
inquiryQueueAdd,
|
||||
inquiryQueueUpdate,
|
||||
inquiryQueueRemove
|
||||
} from '../../../actions/inquiry';
|
||||
|
||||
const removeListener = listener => listener.stop();
|
||||
|
||||
let connectedListener;
|
||||
let disconnectedListener;
|
||||
let queueListener;
|
||||
|
||||
const streamTopic = 'stream-livechat-inquiry-queue-observer';
|
||||
|
||||
export default function subscribeInquiry() {
|
||||
const handleConnection = () => {
|
||||
store.dispatch(inquiryRequest());
|
||||
};
|
||||
|
||||
const handleQueueMessageReceived = (ddpMessage) => {
|
||||
const [{ type, ...sub }] = ddpMessage.fields.args;
|
||||
|
||||
// added can be ignored, since it is handled by 'changed' event
|
||||
if (/added/.test(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the sub isn't on the queue anymore
|
||||
if (sub.status !== 'queued') {
|
||||
// remove it from the queue
|
||||
store.dispatch(inquiryQueueRemove(sub._id));
|
||||
return;
|
||||
}
|
||||
|
||||
const { queued } = store.getState().inquiry;
|
||||
// check if this sub is on the current queue
|
||||
const idx = queued.findIndex(item => item._id === sub._id);
|
||||
if (idx >= 0) {
|
||||
// if it is on the queue let's update
|
||||
store.dispatch(inquiryQueueUpdate(sub));
|
||||
} else {
|
||||
// if it is not on the queue let's add
|
||||
store.dispatch(inquiryQueueAdd(sub));
|
||||
}
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
if (connectedListener) {
|
||||
connectedListener.then(removeListener);
|
||||
connectedListener = false;
|
||||
}
|
||||
if (disconnectedListener) {
|
||||
disconnectedListener.then(removeListener);
|
||||
disconnectedListener = false;
|
||||
}
|
||||
if (queueListener) {
|
||||
queueListener.then(removeListener);
|
||||
queueListener = false;
|
||||
}
|
||||
};
|
||||
|
||||
connectedListener = this.sdk.onStreamData('connected', handleConnection);
|
||||
disconnectedListener = this.sdk.onStreamData('close', handleConnection);
|
||||
queueListener = this.sdk.onStreamData(streamTopic, handleQueueMessageReceived);
|
||||
|
||||
try {
|
||||
const { user } = store.getState().login;
|
||||
RocketChat.getAgentDepartments(user.id).then((result) => {
|
||||
if (result.success) {
|
||||
const { departments } = result;
|
||||
|
||||
if (!departments.length || RocketChat.hasRole('livechat-manager')) {
|
||||
this.sdk.subscribe(streamTopic, 'public').catch(e => console.log(e));
|
||||
}
|
||||
|
||||
const departmentIds = departments.map(({ departmentId }) => departmentId);
|
||||
departmentIds.forEach((departmentId) => {
|
||||
// subscribe to all departments of the agent
|
||||
this.sdk.subscribe(streamTopic, `department/${ departmentId }`).catch(e => console.log(e));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
stop: () => stop()
|
||||
};
|
||||
} catch (e) {
|
||||
log(e);
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import {
|
|||
} from '../actions/share';
|
||||
|
||||
import subscribeRooms from './methods/subscriptions/rooms';
|
||||
import subscribeInquiry from './methods/subscriptions/inquiry';
|
||||
import getUsersPresence, { getUserPresence, subscribeUsersPresence } from './methods/getUsersPresence';
|
||||
|
||||
import protectedFunction from './methods/helpers/protectedFunction';
|
||||
|
@ -72,6 +73,15 @@ const RocketChat = {
|
|||
}
|
||||
}
|
||||
},
|
||||
async subscribeInquiry() {
|
||||
if (!this.inquirySub) {
|
||||
try {
|
||||
this.inquirySub = await subscribeInquiry.call(this);
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
canOpenRoom,
|
||||
createChannel({
|
||||
name, users, type, readOnly, broadcast
|
||||
|
@ -203,6 +213,11 @@ const RocketChat = {
|
|||
this.roomsSub = null;
|
||||
}
|
||||
|
||||
if (this.inquirySub) {
|
||||
this.inquirySub.stop();
|
||||
this.inquirySub = null;
|
||||
}
|
||||
|
||||
if (this.sdk) {
|
||||
this.sdk.disconnect();
|
||||
this.sdk = null;
|
||||
|
@ -680,7 +695,7 @@ const RocketChat = {
|
|||
c: 'channel',
|
||||
d: 'direct'
|
||||
}[room.t];
|
||||
return `${ server }/${ roomType }/${ room.name }?msg=${ message.id }`;
|
||||
return `${ server }/${ roomType }/${ this.isGroupChat(room) ? room.rid : room.name }?msg=${ message.id }`;
|
||||
},
|
||||
getPermalinkChannel(channel) {
|
||||
const { server } = reduxStore.getState().server;
|
||||
|
@ -816,7 +831,7 @@ const RocketChat = {
|
|||
},
|
||||
getAgentDepartments(uid) {
|
||||
// RC 2.4.0
|
||||
return this.sdk.get(`livechat/agents/${ uid }/departments`);
|
||||
return this.sdk.get(`livechat/agents/${ uid }/departments?enabledDepartmentsOnly=true`);
|
||||
},
|
||||
getCustomFields() {
|
||||
// RC 2.2.0
|
||||
|
@ -826,6 +841,16 @@ const RocketChat = {
|
|||
// RC 0.26.0
|
||||
return this.methodCallWrapper('livechat:changeLivechatStatus');
|
||||
},
|
||||
getInquiriesQueued() {
|
||||
// RC 2.4.0
|
||||
return this.sdk.get('livechat/inquiries.queued');
|
||||
},
|
||||
takeInquiry(inquiryId) {
|
||||
// this inquiry is added to the db by the subscriptions stream
|
||||
// and will be removed by the queue stream
|
||||
// RC 2.4.0
|
||||
return this.methodCallWrapper('livechat:takeInquiry', inquiryId);
|
||||
},
|
||||
|
||||
getUidDirectMessage(room) {
|
||||
const { id: userId } = reduxStore.getState().login.user;
|
||||
|
@ -845,6 +870,12 @@ const RocketChat = {
|
|||
return other && other.length ? other[0] : me;
|
||||
},
|
||||
|
||||
isRead(item) {
|
||||
let isUnread = item.archived !== true && item.open === true; // item is not archived and not opened
|
||||
isUnread = isUnread && (item.unread > 0 || item.alert === true); // either its unread count > 0 or its alert
|
||||
return !isUnread;
|
||||
},
|
||||
|
||||
isGroupChat(room) {
|
||||
return (room.uids && room.uids.length > 2) || (room.usernames && room.usernames.length > 2);
|
||||
},
|
||||
|
@ -957,6 +988,14 @@ const RocketChat = {
|
|||
// RC 0.47.0
|
||||
return this.sdk.get('chat.getMessage', { msgId });
|
||||
},
|
||||
hasRole(role) {
|
||||
const shareUser = reduxStore.getState().share.user;
|
||||
const loginUser = reduxStore.getState().login.user;
|
||||
// get user roles on the server from redux
|
||||
const userRoles = (shareUser?.roles || loginUser?.roles) || [];
|
||||
|
||||
return userRoles.indexOf(r => r === role) > -1;
|
||||
},
|
||||
async hasPermission(permissions, rid) {
|
||||
const db = database.active;
|
||||
const subsCollection = db.collections.get('subscriptions');
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -10,7 +10,7 @@ export const onNotification = (notification) => {
|
|||
if (data) {
|
||||
try {
|
||||
const {
|
||||
rid, name, sender, type, host
|
||||
rid, name, sender, type, host, messageType
|
||||
} = EJSON.parse(data.ejson);
|
||||
|
||||
const types = {
|
||||
|
@ -24,7 +24,8 @@ export const onNotification = (notification) => {
|
|||
const params = {
|
||||
host,
|
||||
rid,
|
||||
path: `${ types[type] }/${ roomName }`
|
||||
path: `${ types[type] }/${ roomName }`,
|
||||
isCall: messageType === 'jitsi_call_started'
|
||||
};
|
||||
store.dispatch(deepLinkingOpen(params));
|
||||
} catch (e) {
|
||||
|
|
|
@ -6,7 +6,7 @@ export const ImageComponent = (type) => {
|
|||
const { Image } = require('react-native');
|
||||
Component = Image;
|
||||
} else {
|
||||
const FastImage = require('react-native-fast-image').default;
|
||||
const FastImage = require('@rocket.chat/react-native-fast-image').default;
|
||||
Component = FastImage;
|
||||
}
|
||||
return Component;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
||||
import { KeyboardAwareScrollView } from '@codler/react-native-keyboard-aware-scroll-view';
|
||||
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||
|
||||
export default class KeyboardView extends React.PureComponent {
|
||||
|
|
|
@ -107,7 +107,7 @@ export const RightActions = React.memo(({
|
|||
>
|
||||
<RectButton style={[styles.actionButton, { backgroundColor: themes[theme].hideBackground }]} onPress={onHidePress}>
|
||||
<>
|
||||
<CustomIcon size={20} name='eye-off' color={themes[theme].buttonText} />
|
||||
<CustomIcon size={20} name='unread-on-top-disabled' color={themes[theme].buttonText} />
|
||||
<Text style={[styles.actionText, { color: themes[theme].buttonText }]}>{I18n.t('Hide')}</Text>
|
||||
</>
|
||||
</RectButton>
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
import Wrapper from './Wrapper';
|
||||
import UnreadBadge from '../UnreadBadge';
|
||||
import TypeIcon from './TypeIcon';
|
||||
import LastMessage from './LastMessage';
|
||||
import Title from './Title';
|
||||
import UpdatedAt from './UpdatedAt';
|
||||
import Touchable from './Touchable';
|
||||
|
||||
const RoomItem = ({
|
||||
rid,
|
||||
type,
|
||||
prid,
|
||||
name,
|
||||
avatar,
|
||||
width,
|
||||
avatarSize,
|
||||
baseUrl,
|
||||
userId,
|
||||
username,
|
||||
token,
|
||||
showLastMessage,
|
||||
status,
|
||||
useRealName,
|
||||
theme,
|
||||
isFocused,
|
||||
isGroupChat,
|
||||
isRead,
|
||||
date,
|
||||
accessibilityLabel,
|
||||
favorite,
|
||||
lastMessage,
|
||||
alert,
|
||||
hideUnreadStatus,
|
||||
unread,
|
||||
userMentions,
|
||||
groupMentions,
|
||||
roomUpdatedAt,
|
||||
testID,
|
||||
swipeEnabled,
|
||||
onPress,
|
||||
toggleFav,
|
||||
toggleRead,
|
||||
hideChannel
|
||||
}) => (
|
||||
<Touchable
|
||||
onPress={onPress}
|
||||
width={width}
|
||||
favorite={favorite}
|
||||
toggleFav={toggleFav}
|
||||
isRead={isRead}
|
||||
rid={rid}
|
||||
toggleRead={toggleRead}
|
||||
hideChannel={hideChannel}
|
||||
testID={testID}
|
||||
type={type}
|
||||
theme={theme}
|
||||
isFocused={isFocused}
|
||||
swipeEnabled={swipeEnabled}
|
||||
>
|
||||
<Wrapper
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
avatar={avatar}
|
||||
avatarSize={avatarSize}
|
||||
type={type}
|
||||
baseUrl={baseUrl}
|
||||
userId={userId}
|
||||
token={token}
|
||||
theme={theme}
|
||||
>
|
||||
{showLastMessage
|
||||
? (
|
||||
<>
|
||||
<View style={styles.titleContainer}>
|
||||
<TypeIcon
|
||||
type={type}
|
||||
prid={prid}
|
||||
status={status}
|
||||
isGroupChat={isGroupChat}
|
||||
theme={theme}
|
||||
/>
|
||||
<Title
|
||||
name={name}
|
||||
theme={theme}
|
||||
hideUnreadStatus={hideUnreadStatus}
|
||||
alert={alert}
|
||||
/>
|
||||
<UpdatedAt
|
||||
roomUpdatedAt={roomUpdatedAt}
|
||||
date={date}
|
||||
theme={theme}
|
||||
hideUnreadStatus={hideUnreadStatus}
|
||||
alert={alert}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<LastMessage
|
||||
lastMessage={lastMessage}
|
||||
type={type}
|
||||
showLastMessage={showLastMessage}
|
||||
username={username}
|
||||
alert={alert && !hideUnreadStatus}
|
||||
useRealName={useRealName}
|
||||
theme={theme}
|
||||
/>
|
||||
<UnreadBadge
|
||||
unread={unread}
|
||||
userMentions={userMentions}
|
||||
groupMentions={groupMentions}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<View style={[styles.titleContainer, styles.flex]}>
|
||||
<TypeIcon
|
||||
type={type}
|
||||
prid={prid}
|
||||
status={status}
|
||||
isGroupChat={isGroupChat}
|
||||
theme={theme}
|
||||
/>
|
||||
<Title
|
||||
name={name}
|
||||
theme={theme}
|
||||
hideUnreadStatus={hideUnreadStatus}
|
||||
alert={alert}
|
||||
/>
|
||||
<UnreadBadge
|
||||
unread={unread}
|
||||
userMentions={userMentions}
|
||||
groupMentions={groupMentions}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
</Wrapper>
|
||||
</Touchable>
|
||||
);
|
||||
|
||||
RoomItem.propTypes = {
|
||||
rid: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
prid: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
avatar: PropTypes.string.isRequired,
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
showLastMessage: PropTypes.bool,
|
||||
userId: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
token: PropTypes.string,
|
||||
avatarSize: PropTypes.number,
|
||||
testID: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
status: PropTypes.string,
|
||||
useRealName: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
isFocused: PropTypes.bool,
|
||||
isGroupChat: PropTypes.bool,
|
||||
isRead: PropTypes.bool,
|
||||
date: PropTypes.string,
|
||||
accessibilityLabel: PropTypes.string,
|
||||
lastMessage: PropTypes.object,
|
||||
favorite: PropTypes.bool,
|
||||
alert: PropTypes.bool,
|
||||
hideUnreadStatus: PropTypes.bool,
|
||||
unread: PropTypes.number,
|
||||
userMentions: PropTypes.number,
|
||||
groupMentions: PropTypes.number,
|
||||
roomUpdatedAt: PropTypes.instanceOf(Date),
|
||||
swipeEnabled: PropTypes.bool,
|
||||
toggleFav: PropTypes.func,
|
||||
toggleRead: PropTypes.func,
|
||||
onPress: PropTypes.func,
|
||||
hideChannel: PropTypes.func
|
||||
};
|
||||
|
||||
RoomItem.defaultProps = {
|
||||
avatarSize: 48,
|
||||
status: 'offline',
|
||||
swipeEnabled: true
|
||||
};
|
||||
|
||||
export default RoomItem;
|
|
@ -0,0 +1,31 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from './styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
|
||||
const Title = React.memo(({
|
||||
name, theme, hideUnreadStatus, alert
|
||||
}) => (
|
||||
<Text
|
||||
style={[
|
||||
styles.title,
|
||||
alert && !hideUnreadStatus && styles.alert,
|
||||
{ color: themes[theme].titleText }
|
||||
]}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
));
|
||||
|
||||
Title.propTypes = {
|
||||
name: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
hideUnreadStatus: PropTypes.bool,
|
||||
alert: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Title;
|
|
@ -26,7 +26,8 @@ class Touchable extends React.Component {
|
|||
hideChannel: PropTypes.func,
|
||||
children: PropTypes.element,
|
||||
theme: PropTypes.string,
|
||||
isFocused: PropTypes.bool
|
||||
isFocused: PropTypes.bool,
|
||||
swipeEnabled: PropTypes.bool
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -168,7 +169,7 @@ class Touchable extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
testID, isRead, width, favorite, children, theme, isFocused
|
||||
testID, isRead, width, favorite, children, theme, isFocused, swipeEnabled
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -177,6 +178,7 @@ class Touchable extends React.Component {
|
|||
minDeltaX={20}
|
||||
onGestureEvent={this._onGestureEvent}
|
||||
onHandlerStateChange={this._onHandlerStateChange}
|
||||
enabled={swipeEnabled}
|
||||
>
|
||||
<Animated.View>
|
||||
<LeftActions
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
|
||||
const UnreadBadge = React.memo(({
|
||||
theme, unread, userMentions, type
|
||||
}) => {
|
||||
if (!unread || unread <= 0) {
|
||||
return;
|
||||
}
|
||||
if (unread >= 1000) {
|
||||
unread = '999+';
|
||||
}
|
||||
const mentioned = userMentions > 0 && type !== 'd';
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.unreadNumberContainer,
|
||||
{ backgroundColor: mentioned ? themes[theme].tintColor : themes[theme].borderColor }
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.unreadText,
|
||||
{ color: mentioned ? themes[theme].buttonText : themes[theme].bodyText }
|
||||
]}
|
||||
>{ unread }
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
UnreadBadge.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
unread: PropTypes.number,
|
||||
userMentions: PropTypes.number,
|
||||
type: PropTypes.string
|
||||
};
|
||||
|
||||
export default UnreadBadge;
|
|
@ -0,0 +1,49 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from './styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { capitalize } from '../../utils/room';
|
||||
|
||||
const UpdatedAt = React.memo(({
|
||||
roomUpdatedAt, date, theme, hideUnreadStatus, alert
|
||||
}) => {
|
||||
if (!roomUpdatedAt) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
styles.date,
|
||||
{
|
||||
color:
|
||||
themes[theme]
|
||||
.auxiliaryText
|
||||
},
|
||||
alert && !hideUnreadStatus && [
|
||||
styles.updateAlert,
|
||||
{
|
||||
color:
|
||||
themes[theme]
|
||||
.tintColor
|
||||
}
|
||||
]
|
||||
]}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{capitalize(date)}
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
|
||||
UpdatedAt.propTypes = {
|
||||
roomUpdatedAt: PropTypes.instanceOf(Date),
|
||||
date: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
hideUnreadStatus: PropTypes.bool,
|
||||
alert: PropTypes.bool
|
||||
};
|
||||
|
||||
export default UpdatedAt;
|
|
@ -0,0 +1,58 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from './styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
import Avatar from '../../containers/Avatar';
|
||||
|
||||
const RoomItemInner = ({
|
||||
accessibilityLabel,
|
||||
avatar,
|
||||
avatarSize,
|
||||
type,
|
||||
baseUrl,
|
||||
userId,
|
||||
token,
|
||||
theme,
|
||||
children
|
||||
}) => (
|
||||
<View
|
||||
style={styles.container}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
>
|
||||
<Avatar
|
||||
text={avatar}
|
||||
size={avatarSize}
|
||||
type={type}
|
||||
baseUrl={baseUrl}
|
||||
style={styles.avatar}
|
||||
userId={userId}
|
||||
token={token}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
styles.centerContainer,
|
||||
{
|
||||
borderColor: themes[theme].separatorColor
|
||||
}
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
RoomItemInner.propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
avatar: PropTypes.string,
|
||||
avatarSize: PropTypes.number,
|
||||
type: PropTypes.string,
|
||||
baseUrl: PropTypes.string,
|
||||
userId: PropTypes.string,
|
||||
token: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
children: PropTypes.element
|
||||
};
|
||||
|
||||
export default RoomItemInner;
|
|
@ -1,97 +1,89 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Avatar from '../../containers/Avatar';
|
||||
import I18n from '../../i18n';
|
||||
import styles, { ROW_HEIGHT } from './styles';
|
||||
import UnreadBadge from './UnreadBadge';
|
||||
import TypeIcon from './TypeIcon';
|
||||
import LastMessage from './LastMessage';
|
||||
import { capitalize, formatDate } from '../../utils/room';
|
||||
import Touchable from './Touchable';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { ROW_HEIGHT } from './styles';
|
||||
import { formatDate } from '../../utils/room';
|
||||
import RoomItem from './RoomItem';
|
||||
|
||||
export { ROW_HEIGHT };
|
||||
|
||||
const attrs = [
|
||||
'name',
|
||||
'unread',
|
||||
'userMentions',
|
||||
'showLastMessage',
|
||||
'useRealName',
|
||||
'alert',
|
||||
'type',
|
||||
'width',
|
||||
'isRead',
|
||||
'favorite',
|
||||
'status',
|
||||
'connected',
|
||||
'theme',
|
||||
'isFocused'
|
||||
'isFocused',
|
||||
'forceUpdate',
|
||||
'showLastMessage'
|
||||
];
|
||||
|
||||
const arePropsEqual = (oldProps, newProps) => {
|
||||
const { _updatedAt: _updatedAtOld } = oldProps;
|
||||
const { _updatedAt: _updatedAtNew } = newProps;
|
||||
if (_updatedAtOld && _updatedAtNew && _updatedAtOld.toISOString() !== _updatedAtNew.toISOString()) {
|
||||
return false;
|
||||
}
|
||||
return attrs.every(key => oldProps[key] === newProps[key]);
|
||||
};
|
||||
const arePropsEqual = (oldProps, newProps) => attrs.every(key => oldProps[key] === newProps[key]);
|
||||
|
||||
const RoomItem = React.memo(({
|
||||
const RoomItemContainer = React.memo(({
|
||||
item,
|
||||
onPress,
|
||||
width,
|
||||
favorite,
|
||||
toggleFav,
|
||||
isRead,
|
||||
rid,
|
||||
toggleRead,
|
||||
hideChannel,
|
||||
testID,
|
||||
unread,
|
||||
userMentions,
|
||||
name,
|
||||
_updatedAt,
|
||||
alert,
|
||||
type,
|
||||
avatarSize,
|
||||
baseUrl,
|
||||
userId,
|
||||
username,
|
||||
token,
|
||||
id,
|
||||
prid,
|
||||
showLastMessage,
|
||||
hideUnreadStatus,
|
||||
lastMessage,
|
||||
status,
|
||||
avatar,
|
||||
useRealName,
|
||||
getUserPresence,
|
||||
isGroupChat,
|
||||
connected,
|
||||
theme,
|
||||
isFocused
|
||||
isFocused,
|
||||
getRoomTitle,
|
||||
getRoomAvatar,
|
||||
getIsGroupChat,
|
||||
getIsRead,
|
||||
swipeEnabled
|
||||
}) => {
|
||||
const [, setForceUpdate] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
if (connected && type === 'd' && id) {
|
||||
if (connected && item.t === 'd' && id) {
|
||||
getUserPresence(id);
|
||||
}
|
||||
}, [connected]);
|
||||
|
||||
const date = lastMessage && formatDate(lastMessage.ts);
|
||||
useEffect(() => {
|
||||
if (item?.observe) {
|
||||
const observable = item.observe();
|
||||
const subscription = observable?.subscribe?.(() => {
|
||||
setForceUpdate(prevForceUpdate => prevForceUpdate + 1);
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription?.unsubscribe?.();
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
const name = getRoomTitle(item);
|
||||
const avatar = getRoomAvatar(item);
|
||||
const isGroupChat = getIsGroupChat(item);
|
||||
const isRead = getIsRead(item);
|
||||
const _onPress = () => onPress(item);
|
||||
const date = item.lastMessage?.ts && formatDate(item.lastMessage.ts);
|
||||
|
||||
let accessibilityLabel = name;
|
||||
if (unread === 1) {
|
||||
accessibilityLabel += `, ${ unread } ${ I18n.t('alert') }`;
|
||||
} else if (unread > 1) {
|
||||
accessibilityLabel += `, ${ unread } ${ I18n.t('alerts') }`;
|
||||
if (item.unread === 1) {
|
||||
accessibilityLabel += `, ${ item.unread } ${ I18n.t('alert') }`;
|
||||
} else if (item.unread > 1) {
|
||||
accessibilityLabel += `, ${ item.unread } ${ I18n.t('alerts') }`;
|
||||
}
|
||||
|
||||
if (userMentions > 0) {
|
||||
if (item.userMentions > 0) {
|
||||
accessibilityLabel += `, ${ I18n.t('you_were_mentioned') }`;
|
||||
}
|
||||
|
||||
|
@ -100,120 +92,50 @@ const RoomItem = React.memo(({
|
|||
}
|
||||
|
||||
return (
|
||||
<Touchable
|
||||
onPress={onPress}
|
||||
width={width}
|
||||
favorite={favorite}
|
||||
toggleFav={toggleFav}
|
||||
<RoomItem
|
||||
name={name}
|
||||
avatar={avatar}
|
||||
isGroupChat={isGroupChat}
|
||||
isRead={isRead}
|
||||
rid={rid}
|
||||
onPress={_onPress}
|
||||
date={date}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
userMentions={item.userMentions}
|
||||
width={width}
|
||||
favorite={item.f}
|
||||
toggleFav={toggleFav}
|
||||
rid={item.rid}
|
||||
toggleRead={toggleRead}
|
||||
hideChannel={hideChannel}
|
||||
testID={testID}
|
||||
type={type}
|
||||
type={item.t}
|
||||
theme={theme}
|
||||
isFocused={isFocused}
|
||||
>
|
||||
<View
|
||||
style={styles.container}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
>
|
||||
<Avatar
|
||||
text={avatar}
|
||||
size={avatarSize}
|
||||
type={type}
|
||||
baseUrl={baseUrl}
|
||||
style={styles.avatar}
|
||||
userId={userId}
|
||||
token={token}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
styles.centerContainer,
|
||||
{
|
||||
borderColor: themes[theme].separatorColor
|
||||
}
|
||||
]}
|
||||
>
|
||||
<View style={styles.titleContainer}>
|
||||
<TypeIcon
|
||||
type={type}
|
||||
prid={prid}
|
||||
status={status}
|
||||
isGroupChat={isGroupChat}
|
||||
theme={theme}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
styles.title,
|
||||
alert && !hideUnreadStatus && styles.alert,
|
||||
{ color: themes[theme].titleText }
|
||||
]}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
{_updatedAt ? (
|
||||
<Text
|
||||
style={[
|
||||
styles.date,
|
||||
{
|
||||
color:
|
||||
themes[theme]
|
||||
.auxiliaryText
|
||||
},
|
||||
alert && !hideUnreadStatus && [
|
||||
styles.updateAlert,
|
||||
{
|
||||
color:
|
||||
themes[theme]
|
||||
.tintColor
|
||||
}
|
||||
]
|
||||
]}
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
>
|
||||
{capitalize(date)}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
<View style={styles.row}>
|
||||
<LastMessage
|
||||
lastMessage={lastMessage}
|
||||
type={type}
|
||||
showLastMessage={showLastMessage}
|
||||
username={username}
|
||||
alert={alert && !hideUnreadStatus}
|
||||
useRealName={useRealName}
|
||||
theme={theme}
|
||||
/>
|
||||
<UnreadBadge
|
||||
unread={unread}
|
||||
userMentions={userMentions}
|
||||
type={type}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Touchable>
|
||||
size={avatarSize}
|
||||
baseUrl={baseUrl}
|
||||
userId={userId}
|
||||
token={token}
|
||||
prid={item.prid}
|
||||
status={status}
|
||||
hideUnreadStatus={item.hideUnreadStatus}
|
||||
alert={item.alert}
|
||||
roomUpdatedAt={item.roomUpdatedAt}
|
||||
lastMessage={item.lastMessage}
|
||||
showLastMessage={showLastMessage}
|
||||
username={username}
|
||||
useRealName={useRealName}
|
||||
unread={item.unread}
|
||||
groupMentions={item.groupMentions}
|
||||
swipeEnabled={swipeEnabled}
|
||||
/>
|
||||
);
|
||||
}, arePropsEqual);
|
||||
|
||||
RoomItem.propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
RoomItemContainer.propTypes = {
|
||||
item: PropTypes.object.isRequired,
|
||||
baseUrl: PropTypes.string.isRequired,
|
||||
showLastMessage: PropTypes.bool,
|
||||
_updatedAt: PropTypes.string,
|
||||
lastMessage: PropTypes.object,
|
||||
alert: PropTypes.bool,
|
||||
unread: PropTypes.number,
|
||||
userMentions: PropTypes.number,
|
||||
id: PropTypes.string,
|
||||
prid: PropTypes.string,
|
||||
onPress: PropTypes.func,
|
||||
userId: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
|
@ -221,27 +143,31 @@ RoomItem.propTypes = {
|
|||
avatarSize: PropTypes.number,
|
||||
testID: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
favorite: PropTypes.bool,
|
||||
isRead: PropTypes.bool,
|
||||
rid: PropTypes.string,
|
||||
status: PropTypes.string,
|
||||
toggleFav: PropTypes.func,
|
||||
toggleRead: PropTypes.func,
|
||||
hideChannel: PropTypes.func,
|
||||
avatar: PropTypes.bool,
|
||||
hideUnreadStatus: PropTypes.bool,
|
||||
useRealName: PropTypes.bool,
|
||||
getUserPresence: PropTypes.func,
|
||||
connected: PropTypes.bool,
|
||||
isGroupChat: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
isFocused: PropTypes.bool
|
||||
isFocused: PropTypes.bool,
|
||||
getRoomTitle: PropTypes.func,
|
||||
getRoomAvatar: PropTypes.func,
|
||||
getIsGroupChat: PropTypes.func,
|
||||
getIsRead: PropTypes.func,
|
||||
swipeEnabled: PropTypes.bool
|
||||
};
|
||||
|
||||
RoomItem.defaultProps = {
|
||||
RoomItemContainer.defaultProps = {
|
||||
avatarSize: 48,
|
||||
status: 'offline',
|
||||
getUserPresence: () => {}
|
||||
getUserPresence: () => {},
|
||||
getRoomTitle: () => 'title',
|
||||
getRoomAvatar: () => '',
|
||||
getIsGroupChat: () => false,
|
||||
getIsRead: () => false,
|
||||
swipeEnabled: true
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
|
@ -260,4 +186,4 @@ const mapStateToProps = (state, ownProps) => {
|
|||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(RoomItem);
|
||||
export default connect(mapStateToProps)(RoomItemContainer);
|
||||
|
|
|
@ -8,6 +8,9 @@ export const SMALL_SWIPE = ACTION_WIDTH / 2;
|
|||
export const LONG_SWIPE = ACTION_WIDTH * 3;
|
||||
|
||||
export default StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1
|
||||
},
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
@ -48,23 +51,6 @@ export default StyleSheet.create({
|
|||
updateAlert: {
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
unreadNumberContainer: {
|
||||
minWidth: 21,
|
||||
height: 21,
|
||||
paddingVertical: 3,
|
||||
paddingHorizontal: 5,
|
||||
borderRadius: 10.5,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 10
|
||||
},
|
||||
unreadText: {
|
||||
overflow: 'hidden',
|
||||
fontSize: 13,
|
||||
...sharedStyles.textMedium,
|
||||
letterSpacing: 0.56,
|
||||
textAlign: 'center'
|
||||
},
|
||||
status: {
|
||||
marginLeft: 4,
|
||||
marginRight: 7,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
|
||||
import Touch from '../../utils/touch';
|
||||
import Check from '../../containers/Check';
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
|
||||
import sharedStyles from '../views/Styles';
|
||||
import { themes } from '../constants/colors';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
unreadNumberContainer: {
|
||||
minWidth: 21,
|
||||
height: 21,
|
||||
paddingVertical: 3,
|
||||
paddingHorizontal: 5,
|
||||
borderRadius: 10.5,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginLeft: 10
|
||||
},
|
||||
unreadText: {
|
||||
overflow: 'hidden',
|
||||
fontSize: 13,
|
||||
...sharedStyles.textMedium,
|
||||
letterSpacing: 0.56,
|
||||
textAlign: 'center'
|
||||
}
|
||||
});
|
||||
|
||||
const UnreadBadge = React.memo(({
|
||||
theme, unread, userMentions, groupMentions, style
|
||||
}) => {
|
||||
if (!unread || unread <= 0) {
|
||||
return;
|
||||
}
|
||||
if (unread >= 1000) {
|
||||
unread = '999+';
|
||||
}
|
||||
|
||||
let backgroundColor = themes[theme].unreadBackground;
|
||||
const color = themes[theme].buttonText;
|
||||
if (userMentions > 0) {
|
||||
backgroundColor = themes[theme].mentionMeColor;
|
||||
} else if (groupMentions > 0) {
|
||||
backgroundColor = themes[theme].mentionGroupColor;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.unreadNumberContainer,
|
||||
{ backgroundColor },
|
||||
style
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.unreadText,
|
||||
{ color }
|
||||
]}
|
||||
>{unread}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
UnreadBadge.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
unread: PropTypes.number,
|
||||
userMentions: PropTypes.number,
|
||||
groupMentions: PropTypes.number,
|
||||
style: PropTypes.object
|
||||
};
|
||||
|
||||
export default UnreadBadge;
|
|
@ -1,13 +1,14 @@
|
|||
import React from 'react';
|
||||
import { Text, View, StyleSheet } from 'react-native';
|
||||
import {
|
||||
Text, View, StyleSheet, Pressable
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Avatar from '../containers/Avatar';
|
||||
import { CustomIcon } from '../lib/Icons';
|
||||
import sharedStyles from '../views/Styles';
|
||||
import { themes } from '../constants/colors';
|
||||
import Touch from '../utils/touch';
|
||||
import LongPress from '../utils/longPress';
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
|
@ -43,23 +44,28 @@ const styles = StyleSheet.create({
|
|||
const UserItem = ({
|
||||
name, username, onPress, testID, onLongPress, style, icon, baseUrl, user, theme
|
||||
}) => (
|
||||
<LongPress onLongPress={onLongPress}>
|
||||
<Touch
|
||||
onPress={onPress}
|
||||
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||
testID={testID}
|
||||
theme={theme}
|
||||
>
|
||||
<View style={[styles.container, styles.button, style]}>
|
||||
<Avatar text={username} size={30} type='d' style={styles.avatar} baseUrl={baseUrl} userId={user.id} token={user.token} />
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={[styles.name, { color: themes[theme].titleText }]} numberOfLines={1}>{name}</Text>
|
||||
<Text style={[styles.username, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>@{username}</Text>
|
||||
</View>
|
||||
{icon ? <CustomIcon name={icon} size={22} style={[styles.icon, { color: themes[theme].actionTintColor }]} /> : null}
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
testID={testID}
|
||||
android_ripple={{
|
||||
color: themes[theme].bannerBackground
|
||||
}}
|
||||
style={({ pressed }) => ({
|
||||
backgroundColor: isIOS && pressed
|
||||
? themes[theme].bannerBackground
|
||||
: 'transparent'
|
||||
})}
|
||||
>
|
||||
<View style={[styles.container, styles.button, style]}>
|
||||
<Avatar text={username} size={30} type='d' style={styles.avatar} baseUrl={baseUrl} userId={user.id} token={user.token} />
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={[styles.name, { color: themes[theme].titleText }]} numberOfLines={1}>{name}</Text>
|
||||
<Text style={[styles.username, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>@{username}</Text>
|
||||
</View>
|
||||
</Touch>
|
||||
</LongPress>
|
||||
{icon ? <CustomIcon name={icon} size={22} style={[styles.icon, { color: themes[theme].actionTintColor }]} /> : null}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
|
||||
UserItem.propTypes = {
|
||||
|
|
|
@ -16,6 +16,7 @@ import activeUsers from './activeUsers';
|
|||
import usersTyping from './usersTyping';
|
||||
import inviteLinks from './inviteLinks';
|
||||
import createDiscussion from './createDiscussion';
|
||||
import inquiry from './inquiry';
|
||||
|
||||
export default combineReducers({
|
||||
settings,
|
||||
|
@ -34,5 +35,6 @@ export default combineReducers({
|
|||
activeUsers,
|
||||
usersTyping,
|
||||
inviteLinks,
|
||||
createDiscussion
|
||||
createDiscussion,
|
||||
inquiry
|
||||
});
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import { INQUIRY } from '../actions/actionsTypes';
|
||||
|
||||
const initialState = {
|
||||
enabled: false,
|
||||
queued: [],
|
||||
error: {}
|
||||
};
|
||||
|
||||
export default function inquiry(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case INQUIRY.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
queued: action.inquiries
|
||||
};
|
||||
case INQUIRY.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
error: action.error
|
||||
};
|
||||
case INQUIRY.SET_ENABLED:
|
||||
return {
|
||||
...state,
|
||||
enabled: action.enabled
|
||||
};
|
||||
case INQUIRY.QUEUE_ADD:
|
||||
return {
|
||||
...state,
|
||||
queued: [...state.queued, action.inquiry]
|
||||
};
|
||||
case INQUIRY.QUEUE_UPDATE:
|
||||
return {
|
||||
...state,
|
||||
queued: state.queued.map((item) => {
|
||||
if (item._id === action.inquiry._id) {
|
||||
return action.inquiry;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
};
|
||||
case INQUIRY.QUEUE_REMOVE:
|
||||
return {
|
||||
...state,
|
||||
queued: state.queued.filter(({ _id }) => _id !== action.inquiryId)
|
||||
};
|
||||
case INQUIRY.RESET:
|
||||
return initialState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue