solve merge conflicts
This commit is contained in:
commit
5139d0f5de
|
@ -135,6 +135,10 @@ commands:
|
|||
name: Test
|
||||
command: |
|
||||
npx detox test << parameters.folder >> --configuration ios.sim.release --cleanup
|
||||
when: always
|
||||
|
||||
- store_artifacts:
|
||||
path: ./artifacts
|
||||
|
||||
# JOBS
|
||||
jobs:
|
||||
|
|
|
@ -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... -->
|
||||
|
|
|
@ -58,6 +58,7 @@ buck-out/
|
|||
|
||||
coverage
|
||||
|
||||
artifacts
|
||||
.vscode/
|
||||
e2e/docker/rc_test_env/docker-compose.yml
|
||||
e2e/docker/data/db
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
crashlytics: null
|
||||
};
|
|
@ -4994,137 +4994,6 @@ exports[`Storyshots Message list message 1`] = `
|
|||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`Storyshots RoomItem list roomitem 1`] = `
|
||||
<RCTScrollView
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#efeff4",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"fontSize": 20,
|
||||
"fontWeight": "300",
|
||||
"marginLeft": 10,
|
||||
"marginTop": 30,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"color": "#0d0e12",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Basic
|
||||
</Text>
|
||||
View
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"fontSize": 20,
|
||||
"fontWeight": "300",
|
||||
"marginLeft": 10,
|
||||
"marginTop": 30,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"color": "#0d0e12",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
User
|
||||
</Text>
|
||||
View
|
||||
View
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"fontSize": 20,
|
||||
"fontWeight": "300",
|
||||
"marginLeft": 10,
|
||||
"marginTop": 30,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"color": "#0d0e12",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Type
|
||||
</Text>
|
||||
View
|
||||
View
|
||||
View
|
||||
View
|
||||
View
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"fontSize": 20,
|
||||
"fontWeight": "300",
|
||||
"marginLeft": 10,
|
||||
"marginTop": 30,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"color": "#0d0e12",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Alerts
|
||||
</Text>
|
||||
View
|
||||
View
|
||||
View
|
||||
View
|
||||
View
|
||||
View
|
||||
View
|
||||
View
|
||||
View
|
||||
<Text
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"fontSize": 20,
|
||||
"fontWeight": "300",
|
||||
"marginLeft": 10,
|
||||
"marginTop": 30,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"color": "#0d0e12",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Last Message
|
||||
</Text>
|
||||
View
|
||||
View
|
||||
View
|
||||
View
|
||||
View
|
||||
View
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`Storyshots UiKitMessage list uikitmessage 1`] = `
|
||||
<RCTSafeAreaView
|
||||
emulateUnlessSupported={true}
|
||||
|
|
|
@ -2,13 +2,19 @@ def taskRequests = getGradle().getStartParameter().getTaskRequests().toString().
|
|||
def isPlay = !taskRequests.contains("foss")
|
||||
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
apply plugin: 'kotlin-android'
|
||||
<<<<<<< HEAD
|
||||
|
||||
if (isPlay) {
|
||||
apply plugin: "io.fabric"
|
||||
apply plugin: "com.google.firebase.firebase-perf"
|
||||
apply plugin: 'com.bugsnag.android.gradle'
|
||||
}
|
||||
=======
|
||||
apply plugin: 'com.bugsnag.android.gradle'
|
||||
>>>>>>> e5aaa667e7921a827df304b9d64320e14c7a55d3
|
||||
|
||||
import com.android.build.OutputFile
|
||||
|
||||
|
@ -174,15 +180,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
|
||||
|
||||
|
@ -270,7 +279,11 @@ task copyDownloadableDepsToLibs(type: Copy) {
|
|||
into 'libs'
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
||||
if (isPlay) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
=======
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
||||
>>>>>>> e5aaa667e7921a827df304b9d64320e14c7a55d3
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
@ -24,11 +24,11 @@ buildscript {
|
|||
dependencies {
|
||||
if (isPlay) {
|
||||
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 'com.bugsnag:bugsnag-android-gradle-plugin:4.+'
|
||||
}
|
||||
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ android.useAndroidX=true
|
|||
android.enableJetifier=true
|
||||
|
||||
# Version of flipper SDK to use with React Native
|
||||
FLIPPER_VERSION=0.33.1
|
||||
FLIPPER_VERSION=0.37.0
|
||||
|
||||
# App properties
|
||||
VERSIONCODE=999999999
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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.LOGIN_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.LOGIN_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.LOGIN_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.LOGIN_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.LOGIN_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.LOGIN_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.LOGIN_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.LOGIN_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.LOGIN_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.LOGIN_WITH_SAML);
|
||||
const { server } = this.props;
|
||||
const { clientConfig } = loginService;
|
||||
const { provider } = clientConfig;
|
||||
|
@ -184,6 +195,7 @@ class LoginServices extends React.PureComponent {
|
|||
}
|
||||
|
||||
onPressCas = () => {
|
||||
logEvent(events.LOGIN_WITH_CAS);
|
||||
const { server, CAS_login_url } = this.props;
|
||||
const ssoToken = random(17);
|
||||
const url = `${ CAS_login_url }?service=${ server }/_cas/${ ssoToken }`;
|
||||
|
|
|
@ -122,6 +122,7 @@ class MessageBox extends Component {
|
|||
command: {}
|
||||
};
|
||||
this.text = '';
|
||||
this.selection = { start: 0, end: 0 };
|
||||
this.focused = false;
|
||||
|
||||
// MessageBox Actions
|
||||
|
@ -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 { start, end } = this.selection;
|
||||
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 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) => {
|
||||
|
@ -888,6 +891,7 @@ class MessageBox extends Component {
|
|||
blurOnSubmit={false}
|
||||
placeholder={I18n.t('New_Message')}
|
||||
onChangeText={this.onChangeText}
|
||||
onSelectionChange={this.onSelectionChange}
|
||||
underlineColorAndroid='transparent'
|
||||
defaultValue=''
|
||||
multiline
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -845,6 +845,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);
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
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';
|
||||
|
@ -16,82 +16,79 @@ import { themes } from '../../constants/colors';
|
|||
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(({
|
||||
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
|
||||
}) => {
|
||||
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') }`;
|
||||
}
|
||||
|
||||
|
@ -101,16 +98,16 @@ const RoomItem = React.memo(({
|
|||
|
||||
return (
|
||||
<Touchable
|
||||
onPress={onPress}
|
||||
onPress={_onPress}
|
||||
width={width}
|
||||
favorite={favorite}
|
||||
favorite={item.f}
|
||||
toggleFav={toggleFav}
|
||||
isRead={isRead}
|
||||
rid={rid}
|
||||
rid={item.rid}
|
||||
toggleRead={toggleRead}
|
||||
hideChannel={hideChannel}
|
||||
testID={testID}
|
||||
type={type}
|
||||
type={item.t}
|
||||
theme={theme}
|
||||
isFocused={isFocused}
|
||||
>
|
||||
|
@ -121,7 +118,7 @@ const RoomItem = React.memo(({
|
|||
<Avatar
|
||||
text={avatar}
|
||||
size={avatarSize}
|
||||
type={type}
|
||||
type={item.t}
|
||||
baseUrl={baseUrl}
|
||||
style={styles.avatar}
|
||||
userId={userId}
|
||||
|
@ -137,8 +134,8 @@ const RoomItem = React.memo(({
|
|||
>
|
||||
<View style={styles.titleContainer}>
|
||||
<TypeIcon
|
||||
type={type}
|
||||
prid={prid}
|
||||
type={item.t}
|
||||
prid={item.prid}
|
||||
status={status}
|
||||
isGroupChat={isGroupChat}
|
||||
theme={theme}
|
||||
|
@ -146,7 +143,7 @@ const RoomItem = React.memo(({
|
|||
<Text
|
||||
style={[
|
||||
styles.title,
|
||||
alert && !hideUnreadStatus && styles.alert,
|
||||
item.alert && !item.hideUnreadStatus && styles.alert,
|
||||
{ color: themes[theme].titleText }
|
||||
]}
|
||||
ellipsizeMode='tail'
|
||||
|
@ -154,7 +151,7 @@ const RoomItem = React.memo(({
|
|||
>
|
||||
{name}
|
||||
</Text>
|
||||
{_updatedAt ? (
|
||||
{item.roomUpdatedAt ? (
|
||||
<Text
|
||||
style={[
|
||||
styles.date,
|
||||
|
@ -163,7 +160,7 @@ const RoomItem = React.memo(({
|
|||
themes[theme]
|
||||
.auxiliaryText
|
||||
},
|
||||
alert && !hideUnreadStatus && [
|
||||
item.alert && !item.hideUnreadStatus && [
|
||||
styles.updateAlert,
|
||||
{
|
||||
color:
|
||||
|
@ -181,18 +178,18 @@ const RoomItem = React.memo(({
|
|||
</View>
|
||||
<View style={styles.row}>
|
||||
<LastMessage
|
||||
lastMessage={lastMessage}
|
||||
type={type}
|
||||
lastMessage={item.lastMessage}
|
||||
type={item.t}
|
||||
showLastMessage={showLastMessage}
|
||||
username={username}
|
||||
alert={alert && !hideUnreadStatus}
|
||||
alert={item.alert && !item.hideUnreadStatus}
|
||||
useRealName={useRealName}
|
||||
theme={theme}
|
||||
/>
|
||||
<UnreadBadge
|
||||
unread={unread}
|
||||
userMentions={userMentions}
|
||||
type={type}
|
||||
unread={item.unread}
|
||||
userMentions={item.userMentions}
|
||||
type={item.t}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
|
@ -203,17 +200,10 @@ const RoomItem = React.memo(({
|
|||
}, arePropsEqual);
|
||||
|
||||
RoomItem.propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
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 +211,29 @@ 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
|
||||
};
|
||||
|
||||
RoomItem.defaultProps = {
|
||||
avatarSize: 48,
|
||||
status: 'offline',
|
||||
getUserPresence: () => {}
|
||||
getUserPresence: () => {},
|
||||
getRoomTitle: () => 'title',
|
||||
getRoomAvatar: () => '',
|
||||
getIsGroupChat: () => false,
|
||||
getIsRead: () => false
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
|
|
|
@ -5,12 +5,12 @@ import RNUserDefaults from 'rn-user-defaults';
|
|||
|
||||
import Navigation from '../lib/Navigation';
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import { selectServerRequest } from '../actions/server';
|
||||
import { selectServerRequest, serverInitAdd } from '../actions/server';
|
||||
import { inviteLinksSetToken, inviteLinksRequest } from '../actions/inviteLinks';
|
||||
import database from '../lib/database';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import EventEmitter from '../utils/events';
|
||||
import { appStart, ROOT_INSIDE } from '../actions/app';
|
||||
import { appStart, ROOT_INSIDE, ROOT_NEW_SERVER } from '../actions/app';
|
||||
import { localAuthenticate } from '../utils/localAuthentication';
|
||||
import { goRoom } from '../utils/goRoom';
|
||||
|
||||
|
@ -106,7 +106,8 @@ const handleOpen = function* handleOpen({ params }) {
|
|||
if (!result.success) {
|
||||
return;
|
||||
}
|
||||
Navigation.navigate('NewServerView', { previousServer: server });
|
||||
yield put(appStart({ root: ROOT_NEW_SERVER }));
|
||||
yield put(serverInitAdd(server));
|
||||
yield delay(1000);
|
||||
EventEmitter.emit('NewServer', { server: host });
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { roomsRequest } from '../actions/rooms';
|
||||
import { toMomentLocale } from '../utils/moment';
|
||||
import RocketChat from '../lib/rocketchat';
|
||||
import log from '../utils/log';
|
||||
import log, { logEvent, events } from '../utils/log';
|
||||
import I18n from '../i18n';
|
||||
import database from '../lib/database';
|
||||
import EventEmitter from '../utils/events';
|
||||
|
@ -32,6 +32,7 @@ const loginCall = args => RocketChat.login(args);
|
|||
const logoutCall = args => RocketChat.logout(args);
|
||||
|
||||
const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnError = false }) {
|
||||
logEvent(events.DEFAULT_LOGIN);
|
||||
try {
|
||||
let result;
|
||||
if (credentials.resume) {
|
||||
|
@ -52,6 +53,7 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE
|
|||
if (logoutOnError && (e.data && e.data.message && /you've been logged out by the server/i.test(e.data.message))) {
|
||||
yield put(logout(true));
|
||||
} else {
|
||||
logEvent(events.DEFAULT_LOGIN_FAIL);
|
||||
yield put(loginFailure(e));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,7 +112,6 @@ const ChatsStackNavigator = () => {
|
|||
<ChatsStack.Screen
|
||||
name='MessagesView'
|
||||
component={MessagesView}
|
||||
options={MessagesView.navigationOptions}
|
||||
/>
|
||||
<ChatsStack.Screen
|
||||
name='AutoTranslateView'
|
||||
|
|
|
@ -139,7 +139,6 @@ const ModalStackNavigator = React.memo(({ navigation }) => {
|
|||
<ModalStack.Screen
|
||||
name='MessagesView'
|
||||
component={MessagesView}
|
||||
options={MessagesView.navigationOptions}
|
||||
/>
|
||||
<ModalStack.Screen
|
||||
name='AutoTranslateView'
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
export default {
|
||||
JOIN_A_WORKSPACE: 'join_a_workspace',
|
||||
CREATE_NEW_WORKSPACE: 'create_new_workspace',
|
||||
CREATE_NEW_WORKSPACE_FAIL: 'create_new_workspace_fail',
|
||||
CONNECT_TO_WORKSPACE: 'connect_to_workspace',
|
||||
CONNECT_TO_WORKSPACE_FAIL: 'connect_to_workspace_fail',
|
||||
JOIN_OPEN_WORKSPACE: 'join_open_workspace',
|
||||
DEFAULT_LOGIN: 'default_login',
|
||||
DEFAULT_LOGIN_FAIL: 'default_login_fail',
|
||||
DEFAULT_SIGN_UP: 'default_sign_up',
|
||||
DEFAULT_SIGN_UP_FAIL: 'default_sign_up_fail',
|
||||
FORGOT_PASSWORD: 'forgot_password',
|
||||
LOGIN_WITH_FACEBOOK: 'login_with_facebook',
|
||||
LOGIN_WITH_GITHUB: 'login_with_github',
|
||||
LOGIN_WITH_GITLAB: 'login_with_gitlab',
|
||||
LOGIN_WITH_LINKEDIN: 'login_with_linkedin',
|
||||
LOGIN_WITH_GOOGLE: 'login_with_google',
|
||||
LOGIN_WITH_METEOR: 'login_with_meteor',
|
||||
LOGIN_WITH_TWITTER: 'login_with_twitter',
|
||||
LOGIN_WITH_WORDPRESS: 'login_with_wordpress',
|
||||
LOGIN_WITH_CUSTOM_OAUTH: 'login_with_custom_oauth',
|
||||
LOGIN_WITH_SAML: 'login_with_saml',
|
||||
LOGIN_WITH_CAS: 'login_with_cas'
|
||||
};
|
|
@ -1,16 +1,16 @@
|
|||
import { Client } from 'bugsnag-react-native';
|
||||
import firebase from 'react-native-firebase';
|
||||
import analytics from '@react-native-firebase/analytics';
|
||||
import crashlytics from '@react-native-firebase/crashlytics';
|
||||
import { isGooglePlayBuild } from '../constants/environment';
|
||||
import config from '../../config';
|
||||
import config from '../../../config';
|
||||
import events from './events';
|
||||
|
||||
const bugsnag = new Client(config.BUGSNAG_API_KEY);
|
||||
|
||||
export const analytics = isGooglePlayBuild ? firebase.analytics : ({
|
||||
logEvent: () => { }
|
||||
});
|
||||
|
||||
export { analytics };
|
||||
export const loggerConfig = bugsnag.config;
|
||||
export const { leaveBreadcrumb } = bugsnag;
|
||||
export { events };
|
||||
|
||||
let metadata = {};
|
||||
|
||||
|
@ -20,6 +20,11 @@ export const logServerVersion = (serverVersion) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const logEvent = (eventName, payload) => {
|
||||
analytics().logEvent(eventName, payload);
|
||||
leaveBreadcrumb(eventName, payload);
|
||||
};
|
||||
|
||||
export const setCurrentScreen = (currentScreen) => {
|
||||
if (isGooglePlayBuild) {
|
||||
analytics().setCurrentScreen(currentScreen);
|
||||
|
@ -36,6 +41,7 @@ export default (e) => {
|
|||
}
|
||||
};
|
||||
});
|
||||
crashlytics().recordError(e);
|
||||
} else {
|
||||
console.log(e);
|
||||
}
|
|
@ -31,6 +31,8 @@ class AdminPanelView extends React.Component {
|
|||
<SafeAreaView theme={theme}>
|
||||
<StatusBar theme={theme} />
|
||||
<WebView
|
||||
// https://github.com/react-native-community/react-native-webview/issues/1311
|
||||
onMessage={() => {}}
|
||||
source={{ uri: `${ baseUrl }/admin/info?layout=embedded` }}
|
||||
injectedJavaScript={`Meteor.loginWithToken('${ token }', function() { })`}
|
||||
/>
|
||||
|
|
|
@ -19,11 +19,11 @@ import SafeAreaView from '../containers/SafeAreaView';
|
|||
|
||||
const DEFAULT_BROWSERS = [
|
||||
{
|
||||
title: I18n.t('In_app'),
|
||||
title: 'In_app',
|
||||
value: 'inApp'
|
||||
},
|
||||
{
|
||||
title: isIOS ? 'Safari' : I18n.t('Browser'),
|
||||
title: isIOS ? 'Safari' : 'Browser',
|
||||
value: 'systemDefault:'
|
||||
}
|
||||
];
|
||||
|
@ -137,7 +137,7 @@ class DefaultBrowserView extends React.Component {
|
|||
const { title, value } = item;
|
||||
return (
|
||||
<ListItem
|
||||
title={title}
|
||||
title={I18n.t(title, { defaultValue: title })}
|
||||
onPress={() => this.changeDefaultBrowser(value)}
|
||||
testID={`default-browser-view-${ title }`}
|
||||
right={this.isSelected(value) ? this.renderIcon : null}
|
||||
|
|
|
@ -21,9 +21,6 @@ import SafeAreaView from '../../containers/SafeAreaView';
|
|||
|
||||
const OPTIONS = {
|
||||
days: [{
|
||||
label: I18n.t('Never'), value: 0
|
||||
},
|
||||
{
|
||||
label: '1', value: 1
|
||||
},
|
||||
{
|
||||
|
@ -36,9 +33,6 @@ const OPTIONS = {
|
|||
label: '30', value: 30
|
||||
}],
|
||||
maxUses: [{
|
||||
label: I18n.t('No_limit'), value: 0
|
||||
},
|
||||
{
|
||||
label: '1', value: 1
|
||||
},
|
||||
{
|
||||
|
@ -91,9 +85,12 @@ class InviteUsersView extends React.Component {
|
|||
navigation.pop();
|
||||
}
|
||||
|
||||
renderPicker = (key) => {
|
||||
renderPicker = (key, first) => {
|
||||
const { props } = this;
|
||||
const { theme } = props;
|
||||
const firstEl = [{
|
||||
label: I18n.t(first), value: 0
|
||||
}];
|
||||
return (
|
||||
<RNPickerSelect
|
||||
style={{ viewContainer: styles.viewContainer }}
|
||||
|
@ -102,7 +99,7 @@ class InviteUsersView extends React.Component {
|
|||
useNativeAndroidPickerStyle={false}
|
||||
placeholder={{}}
|
||||
onValueChange={value => this.onValueChangePicker(key, value)}
|
||||
items={OPTIONS[key]}
|
||||
items={firstEl.concat(OPTIONS[key])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -121,13 +118,13 @@ class InviteUsersView extends React.Component {
|
|||
<Separator theme={theme} />
|
||||
<ListItem
|
||||
title={I18n.t('Expiration_Days')}
|
||||
right={() => this.renderPicker('days')}
|
||||
right={() => this.renderPicker('days', 'Never')}
|
||||
theme={theme}
|
||||
/>
|
||||
<Separator theme={theme} />
|
||||
<ListItem
|
||||
title={I18n.t('Max_number_of_uses')}
|
||||
right={() => this.renderPicker('maxUses')}
|
||||
right={() => this.renderPicker('maxUses', 'No_limit')}
|
||||
theme={theme}
|
||||
/>
|
||||
<Separator theme={theme} />
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
import { connect } from 'react-redux';
|
||||
import equal from 'deep-equal';
|
||||
|
||||
import { analytics } from '../utils/log';
|
||||
import { logEvent, events } from '../utils/log';
|
||||
import sharedStyles from './Styles';
|
||||
import Button from '../containers/Button';
|
||||
import I18n from '../i18n';
|
||||
|
@ -17,7 +17,6 @@ import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
|||
import TextInput from '../containers/TextInput';
|
||||
import { loginRequest as loginRequestAction } from '../actions/login';
|
||||
import LoginServices from '../containers/LoginServices';
|
||||
import { isGooglePlayBuild } from '../constants/environment';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
registerDisabled: {
|
||||
|
@ -104,6 +103,7 @@ class LoginView extends React.Component {
|
|||
}
|
||||
|
||||
forgotPassword = () => {
|
||||
logEvent(events.FORGOT_PASSWORD);
|
||||
const { navigation, Site_Name } = this.props;
|
||||
navigation.navigate('ForgotPasswordView', { title: Site_Name });
|
||||
}
|
||||
|
@ -122,9 +122,6 @@ class LoginView extends React.Component {
|
|||
const { loginRequest } = this.props;
|
||||
Keyboard.dismiss();
|
||||
loginRequest({ user, password });
|
||||
if (isGooglePlayBuild) {
|
||||
analytics().logEvent('login');
|
||||
}
|
||||
}
|
||||
|
||||
renderUserForm = () => {
|
||||
|
|
|
@ -18,10 +18,6 @@ import { withActionSheet } from '../../containers/ActionSheet';
|
|||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
|
||||
class MessagesView extends React.Component {
|
||||
static navigationOptions = ({ route }) => ({
|
||||
title: I18n.t(route.params?.name)
|
||||
});
|
||||
|
||||
static propTypes = {
|
||||
user: PropTypes.object,
|
||||
baseUrl: PropTypes.string,
|
||||
|
@ -39,6 +35,7 @@ class MessagesView extends React.Component {
|
|||
messages: [],
|
||||
fileLoading: true
|
||||
};
|
||||
this.setHeader();
|
||||
this.rid = props.route.params?.rid;
|
||||
this.t = props.route.params?.t;
|
||||
this.content = this.defineMessagesViewContent(props.route.params?.name);
|
||||
|
@ -65,10 +62,16 @@ class MessagesView extends React.Component {
|
|||
if (fileLoading !== nextState.fileLoading) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
setHeader = () => {
|
||||
const { route, navigation } = this.props;
|
||||
navigation.setOptions({
|
||||
title: I18n.t(route.params?.name)
|
||||
});
|
||||
}
|
||||
|
||||
navToRoomInfo = (navParam) => {
|
||||
const { navigation, user } = this.props;
|
||||
if (navParam.rid === user.id) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { StyleSheet, View } from 'react-native';
|
|||
import PropTypes from 'prop-types';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import { connect } from 'react-redux';
|
||||
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
|
||||
import { KeyboardAwareScrollView } from '@codler/react-native-keyboard-aware-scroll-view';
|
||||
|
||||
import { withTheme } from '../theme';
|
||||
import EventEmitter from '../utils/events';
|
||||
|
|
|
@ -7,11 +7,12 @@ import { connect } from 'react-redux';
|
|||
import * as FileSystem from 'expo-file-system';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import RNUserDefaults from 'rn-user-defaults';
|
||||
import { encode } from 'base-64';
|
||||
import { Base64 } from 'js-base64';
|
||||
import parse from 'url-parse';
|
||||
|
||||
import EventEmitter from '../utils/events';
|
||||
import { selectServerRequest, serverRequest } from '../actions/server';
|
||||
import { inviteLinksClear as inviteLinksClearAction } from '../actions/inviteLinks';
|
||||
import sharedStyles from './Styles';
|
||||
import Button from '../containers/Button';
|
||||
import TextInput from '../containers/TextInput';
|
||||
|
@ -20,7 +21,7 @@ import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
|||
import I18n from '../i18n';
|
||||
import { isIOS } from '../utils/deviceInfo';
|
||||
import { themes } from '../constants/colors';
|
||||
import log from '../utils/log';
|
||||
import log, { logEvent, events } from '../utils/log';
|
||||
import { animateNextTransition } from '../utils/layoutAnimation';
|
||||
import { withTheme } from '../theme';
|
||||
import { setBasicAuth, BASIC_AUTH_KEY } from '../utils/fetch';
|
||||
|
@ -72,16 +73,13 @@ class NewServerView extends React.Component {
|
|||
connectServer: PropTypes.func.isRequired,
|
||||
selectServer: PropTypes.func.isRequired,
|
||||
adding: PropTypes.bool,
|
||||
previousServer: PropTypes.string
|
||||
previousServer: PropTypes.string,
|
||||
inviteLinksClear: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (props.adding) {
|
||||
props.navigation.setOptions({
|
||||
headerLeft: () => <CloseModalButton navigation={props.navigation} onPress={this.close} testID='new-server-view-close' />
|
||||
});
|
||||
}
|
||||
this.setHeader();
|
||||
|
||||
this.state = {
|
||||
text: '',
|
||||
|
@ -92,11 +90,27 @@ class NewServerView extends React.Component {
|
|||
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { adding } = this.props;
|
||||
if (prevProps.adding !== adding) {
|
||||
this.setHeader();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventEmitter.removeListener('NewServer', this.handleNewServerEvent);
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
||||
setHeader = () => {
|
||||
const { adding, navigation } = this.props;
|
||||
if (adding) {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => <CloseModalButton navigation={navigation} onPress={this.close} testID='new-server-view-close' />
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleBackPress = () => {
|
||||
const { navigation, previousServer } = this.props;
|
||||
if (navigation.isFocused() && previousServer) {
|
||||
|
@ -111,7 +125,8 @@ class NewServerView extends React.Component {
|
|||
}
|
||||
|
||||
close = () => {
|
||||
const { selectServer, previousServer } = this.props;
|
||||
const { selectServer, previousServer, inviteLinksClear } = this.props;
|
||||
inviteLinksClear();
|
||||
selectServer(previousServer);
|
||||
}
|
||||
|
||||
|
@ -124,6 +139,7 @@ class NewServerView extends React.Component {
|
|||
}
|
||||
|
||||
submit = async() => {
|
||||
logEvent(events.CONNECT_TO_WORKSPACE);
|
||||
const { text, certificate } = this.state;
|
||||
const { connectServer } = this.props;
|
||||
let cert = null;
|
||||
|
@ -135,6 +151,7 @@ class NewServerView extends React.Component {
|
|||
try {
|
||||
await FileSystem.copyAsync({ from: certificate.path, to: certificatePath });
|
||||
} catch (e) {
|
||||
logEvent(events.CONNECT_TO_WORKSPACE_FAIL);
|
||||
log(e);
|
||||
}
|
||||
cert = {
|
||||
|
@ -152,6 +169,7 @@ class NewServerView extends React.Component {
|
|||
}
|
||||
|
||||
connectOpen = () => {
|
||||
logEvent(events.JOIN_OPEN_WORKSPACE);
|
||||
this.setState({ connectingOpen: true });
|
||||
const { connectServer } = this.props;
|
||||
connectServer('https://open.rocket.chat');
|
||||
|
@ -161,7 +179,7 @@ class NewServerView extends React.Component {
|
|||
try {
|
||||
const parsedUrl = parse(text, true);
|
||||
if (parsedUrl.auth.length) {
|
||||
const credentials = encode(parsedUrl.auth);
|
||||
const credentials = Base64.encode(parsedUrl.auth);
|
||||
await RNUserDefaults.set(`${ BASIC_AUTH_KEY }-${ server }`, credentials);
|
||||
setBasicAuth(credentials);
|
||||
}
|
||||
|
@ -321,7 +339,8 @@ const mapStateToProps = state => ({
|
|||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
connectServer: (server, certificate) => dispatch(serverRequest(server, certificate)),
|
||||
selectServer: server => dispatch(selectServerRequest(server))
|
||||
selectServer: server => dispatch(selectServerRequest(server)),
|
||||
inviteLinksClear: () => dispatch(inviteLinksClearAction())
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewServerView));
|
||||
|
|
|
@ -71,58 +71,58 @@ Info.propTypes = {
|
|||
|
||||
const OPTIONS = {
|
||||
desktopNotifications: [{
|
||||
label: I18n.t('Default'), value: 'default'
|
||||
label: 'Default', value: 'default'
|
||||
}, {
|
||||
label: I18n.t('All_Messages'), value: 'all'
|
||||
label: 'All_Messages', value: 'all'
|
||||
}, {
|
||||
label: I18n.t('Mentions'), value: 'mentions'
|
||||
label: 'Mentions', value: 'mentions'
|
||||
}, {
|
||||
label: I18n.t('Nothing'), value: 'nothing'
|
||||
label: 'Nothing', value: 'nothing'
|
||||
}],
|
||||
audioNotifications: [{
|
||||
label: I18n.t('Default'), value: 'default'
|
||||
label: 'Default', value: 'default'
|
||||
}, {
|
||||
label: I18n.t('All_Messages'), value: 'all'
|
||||
label: 'All_Messages', value: 'all'
|
||||
}, {
|
||||
label: I18n.t('Mentions'), value: 'mentions'
|
||||
label: 'Mentions', value: 'mentions'
|
||||
}, {
|
||||
label: I18n.t('Nothing'), value: 'nothing'
|
||||
label: 'Nothing', value: 'nothing'
|
||||
}],
|
||||
mobilePushNotifications: [{
|
||||
label: I18n.t('Default'), value: 'default'
|
||||
label: 'Default', value: 'default'
|
||||
}, {
|
||||
label: I18n.t('All_Messages'), value: 'all'
|
||||
label: 'All_Messages', value: 'all'
|
||||
}, {
|
||||
label: I18n.t('Mentions'), value: 'mentions'
|
||||
label: 'Mentions', value: 'mentions'
|
||||
}, {
|
||||
label: I18n.t('Nothing'), value: 'nothing'
|
||||
label: 'Nothing', value: 'nothing'
|
||||
}],
|
||||
emailNotifications: [{
|
||||
label: I18n.t('Default'), value: 'default'
|
||||
label: 'Default', value: 'default'
|
||||
}, {
|
||||
label: I18n.t('All_Messages'), value: 'all'
|
||||
label: 'All_Messages', value: 'all'
|
||||
}, {
|
||||
label: I18n.t('Mentions'), value: 'mentions'
|
||||
label: 'Mentions', value: 'mentions'
|
||||
}, {
|
||||
label: I18n.t('Nothing'), value: 'nothing'
|
||||
label: 'Nothing', value: 'nothing'
|
||||
}],
|
||||
desktopNotificationDuration: [{
|
||||
label: I18n.t('Default'), value: 0
|
||||
label: 'Default', value: 0
|
||||
}, {
|
||||
label: I18n.t('Seconds', { second: 1 }), value: 1
|
||||
label: 'Seconds', second: 1, value: 1
|
||||
}, {
|
||||
label: I18n.t('Seconds', { second: 2 }), value: 2
|
||||
label: 'Seconds', second: 2, value: 2
|
||||
}, {
|
||||
label: I18n.t('Seconds', { second: 3 }), value: 3
|
||||
label: 'Seconds', second: 3, value: 3
|
||||
}, {
|
||||
label: I18n.t('Seconds', { second: 4 }), value: 4
|
||||
label: 'Seconds', second: 4, value: 4
|
||||
}, {
|
||||
label: I18n.t('Seconds', { second: 5 }), value: 5
|
||||
label: 'Seconds', second: 5, value: 5
|
||||
}],
|
||||
audioNotificationValue: [{
|
||||
label: 'None', value: 'none None'
|
||||
}, {
|
||||
label: I18n.t('Default'), value: '0 Default'
|
||||
label: 'Default', value: '0 Default'
|
||||
}, {
|
||||
label: 'Beep', value: 'beep Beep'
|
||||
}, {
|
||||
|
@ -229,7 +229,7 @@ class NotificationPreferencesView extends React.Component {
|
|||
const { room } = this.state;
|
||||
const { theme } = this.props;
|
||||
const text = room[key] ? OPTIONS[key].find(option => option.value === room[key]) : OPTIONS[key][0];
|
||||
return <Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>{text?.label}</Text>;
|
||||
return <Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>{I18n.t(text?.label, { defaultValue: text?.label, second: text?.second })}</Text>;
|
||||
}
|
||||
|
||||
renderSwitch = (key) => {
|
||||
|
|
|
@ -14,6 +14,7 @@ import { isTablet } from '../../utils/deviceInfo';
|
|||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
|
||||
import { logEvent, events } from '../../utils/log';
|
||||
|
||||
class OnboardingView extends React.Component {
|
||||
static navigationOptions = {
|
||||
|
@ -69,15 +70,17 @@ class OnboardingView extends React.Component {
|
|||
}
|
||||
|
||||
connectServer = () => {
|
||||
logEvent(events.JOIN_A_WORKSPACE);
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('NewServerView');
|
||||
}
|
||||
|
||||
createWorkspace = async() => {
|
||||
logEvent(events.CREATE_NEW_WORKSPACE);
|
||||
try {
|
||||
await Linking.openURL('https://cloud.rocket.chat/trial');
|
||||
} catch {
|
||||
// do nothing
|
||||
logEvent(events.CREATE_NEW_WORKSPACE_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ const Item = React.memo(({
|
|||
theme
|
||||
}) => (
|
||||
<ListItem
|
||||
title={item.label}
|
||||
title={I18n.t(item.label, { defaultValue: item.label, second: item?.second })}
|
||||
right={selected && (() => <Check theme={theme} style={styles.check} />)}
|
||||
onPress={onItemPress}
|
||||
theme={theme}
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
import { connect } from 'react-redux';
|
||||
import RNPickerSelect from 'react-native-picker-select';
|
||||
|
||||
import log from '../utils/log';
|
||||
import log, { logEvent, events } from '../utils/log';
|
||||
import sharedStyles from './Styles';
|
||||
import Button from '../containers/Button';
|
||||
import I18n from '../i18n';
|
||||
|
@ -114,6 +114,7 @@ class RegisterView extends React.Component {
|
|||
}
|
||||
|
||||
submit = async() => {
|
||||
logEvent(events.DEFAULT_SIGN_UP);
|
||||
if (!this.valid()) {
|
||||
return;
|
||||
}
|
||||
|
@ -149,6 +150,7 @@ class RegisterView extends React.Component {
|
|||
return loginRequest({ user: email, password });
|
||||
}
|
||||
if (e.data?.error) {
|
||||
logEvent(events.DEFAULT_SIGN_UP_FAIL);
|
||||
showErrorAlert(e.data.error, I18n.t('Oops'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import { FlatList, RefreshControl } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import moment from 'moment';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
@ -15,9 +14,10 @@ import EmptyRoom from './EmptyRoom';
|
|||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import { animateNextTransition } from '../../utils/layoutAnimation';
|
||||
import ActivityIndicator from '../../containers/ActivityIndicator';
|
||||
import debounce from '../../utils/debounce';
|
||||
import { themes } from '../../constants/colors';
|
||||
|
||||
const QUERY_SIZE = 50;
|
||||
|
||||
class List extends React.Component {
|
||||
static propTypes = {
|
||||
onEndReached: PropTypes.func,
|
||||
|
@ -47,7 +47,8 @@ class List extends React.Component {
|
|||
super(props);
|
||||
console.time(`${ this.constructor.name } init`);
|
||||
console.time(`${ this.constructor.name } mount`);
|
||||
|
||||
this.count = 0;
|
||||
this.needsFetch = false;
|
||||
this.mounted = false;
|
||||
this.state = {
|
||||
loading: true,
|
||||
|
@ -56,7 +57,7 @@ class List extends React.Component {
|
|||
refreshing: false,
|
||||
animated: false
|
||||
};
|
||||
this.init();
|
||||
this.query();
|
||||
this.unsubscribeFocus = props.navigation.addListener('focus', () => {
|
||||
if (this.mounted) {
|
||||
this.setState({ animated: true });
|
||||
|
@ -72,72 +73,6 @@ class List extends React.Component {
|
|||
console.timeEnd(`${ this.constructor.name } mount`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
async init() {
|
||||
const { rid, tmid } = this.props;
|
||||
const db = database.active;
|
||||
|
||||
// handle servers with version < 3.0.0
|
||||
let { hideSystemMessages = [] } = this.props;
|
||||
if (!Array.isArray(hideSystemMessages)) {
|
||||
hideSystemMessages = [];
|
||||
}
|
||||
|
||||
if (tmid) {
|
||||
try {
|
||||
this.thread = await db.collections
|
||||
.get('threads')
|
||||
.find(tmid);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
this.messagesObservable = db.collections
|
||||
.get('thread_messages')
|
||||
.query(Q.where('rid', tmid), Q.or(Q.where('t', Q.notIn(hideSystemMessages)), Q.where('t', Q.eq(null))))
|
||||
.observe();
|
||||
} else if (rid) {
|
||||
this.messagesObservable = db.collections
|
||||
.get('messages')
|
||||
.query(Q.where('rid', rid), Q.or(Q.where('t', Q.notIn(hideSystemMessages)), Q.where('t', Q.eq(null))))
|
||||
.observe();
|
||||
}
|
||||
|
||||
if (rid) {
|
||||
this.unsubscribeMessages();
|
||||
this.messagesSubscription = this.messagesObservable
|
||||
.subscribe((data) => {
|
||||
if (tmid && this.thread) {
|
||||
data = [this.thread, ...data];
|
||||
}
|
||||
const messages = orderBy(data, ['ts'], ['desc']);
|
||||
if (this.mounted) {
|
||||
this.setState({ messages }, () => this.update());
|
||||
} else {
|
||||
this.state.messages = messages;
|
||||
}
|
||||
this.readThreads();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
reload = () => {
|
||||
this.unsubscribeMessages();
|
||||
this.init();
|
||||
}
|
||||
|
||||
readThreads = async() => {
|
||||
const { tmid } = this.props;
|
||||
|
||||
if (tmid) {
|
||||
try {
|
||||
await RocketChat.readThreads(tmid);
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { loading, end, refreshing } = this.state;
|
||||
const { hideSystemMessages, theme } = this.props;
|
||||
|
@ -177,7 +112,7 @@ class List extends React.Component {
|
|||
console.countReset(`${ this.constructor.name }.render calls`);
|
||||
}
|
||||
|
||||
onEndReached = debounce(async() => {
|
||||
fetchData = async() => {
|
||||
const {
|
||||
loading, end, messages, latest = messages[messages.length - 1]?.ts
|
||||
} = this.state;
|
||||
|
@ -196,12 +131,99 @@ class List extends React.Component {
|
|||
result = await RocketChat.loadMessagesForRoom({ rid, t, latest });
|
||||
}
|
||||
|
||||
this.setState({ end: result.length < 50, loading: false, latest: result[result.length - 1]?.ts }, () => this.loadMoreMessages(result));
|
||||
this.setState({ end: result.length < QUERY_SIZE, loading: false, latest: result[result.length - 1]?.ts }, () => this.loadMoreMessages(result));
|
||||
} catch (e) {
|
||||
this.setState({ loading: false });
|
||||
log(e);
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
query = async() => {
|
||||
this.count += QUERY_SIZE;
|
||||
const { rid, tmid } = this.props;
|
||||
const db = database.active;
|
||||
|
||||
// handle servers with version < 3.0.0
|
||||
let { hideSystemMessages = [] } = this.props;
|
||||
if (!Array.isArray(hideSystemMessages)) {
|
||||
hideSystemMessages = [];
|
||||
}
|
||||
|
||||
if (tmid) {
|
||||
try {
|
||||
this.thread = await db.collections
|
||||
.get('threads')
|
||||
.find(tmid);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
this.messagesObservable = db.collections
|
||||
.get('thread_messages')
|
||||
.query(
|
||||
Q.where('rid', tmid),
|
||||
Q.experimentalSortBy('ts', Q.desc),
|
||||
Q.experimentalSkip(0),
|
||||
Q.experimentalTake(this.count)
|
||||
)
|
||||
.observe();
|
||||
} else if (rid) {
|
||||
this.messagesObservable = db.collections
|
||||
.get('messages')
|
||||
.query(
|
||||
Q.where('rid', rid),
|
||||
Q.experimentalSortBy('ts', Q.desc),
|
||||
Q.experimentalSkip(0),
|
||||
Q.experimentalTake(this.count)
|
||||
)
|
||||
.observe();
|
||||
}
|
||||
|
||||
if (rid) {
|
||||
this.unsubscribeMessages();
|
||||
this.messagesSubscription = this.messagesObservable
|
||||
.subscribe((messages) => {
|
||||
if (messages.length <= this.count) {
|
||||
this.needsFetch = true;
|
||||
}
|
||||
if (tmid && this.thread) {
|
||||
messages = [...messages, this.thread];
|
||||
}
|
||||
messages = messages.filter(m => !m.t || !hideSystemMessages?.includes(m.t));
|
||||
|
||||
if (this.mounted) {
|
||||
this.setState({ messages }, () => this.update());
|
||||
} else {
|
||||
this.state.messages = messages;
|
||||
}
|
||||
this.readThreads();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
reload = () => {
|
||||
this.count = 0;
|
||||
this.query();
|
||||
}
|
||||
|
||||
readThreads = async() => {
|
||||
const { tmid } = this.props;
|
||||
|
||||
if (tmid) {
|
||||
try {
|
||||
await RocketChat.readThreads(tmid);
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onEndReached = async() => {
|
||||
if (this.needsFetch) {
|
||||
this.needsFetch = false;
|
||||
await this.fetchData();
|
||||
}
|
||||
this.query();
|
||||
}
|
||||
|
||||
loadMoreMessages = (result) => {
|
||||
const { end } = this.state;
|
||||
|
@ -305,7 +327,7 @@ class List extends React.Component {
|
|||
removeClippedSubviews={isIOS}
|
||||
initialNumToRender={7}
|
||||
onEndReached={this.onEndReached}
|
||||
onEndReachedThreshold={5}
|
||||
onEndReachedThreshold={0.5}
|
||||
maxToRenderPerBatch={5}
|
||||
windowSize={10}
|
||||
ListFooterComponent={this.renderFooter}
|
||||
|
|
|
@ -206,12 +206,10 @@ class RoomView extends React.Component {
|
|||
const { appState, insets } = this.props;
|
||||
|
||||
if (appState === 'foreground' && appState !== prevProps.appState && this.rid) {
|
||||
this.onForegroundInteraction = InteractionManager.runAfterInteractions(() => {
|
||||
// Fire List.init() just to keep observables working
|
||||
// Fire List.query() just to keep observables working
|
||||
if (this.list && this.list.current) {
|
||||
this.list.current.init();
|
||||
this.list.current?.query?.();
|
||||
}
|
||||
});
|
||||
}
|
||||
// If it's not direct message
|
||||
if (this.t !== 'd') {
|
||||
|
@ -267,9 +265,6 @@ class RoomView extends React.Component {
|
|||
if (this.didMountInteraction && this.didMountInteraction.cancel) {
|
||||
this.didMountInteraction.cancel();
|
||||
}
|
||||
if (this.onForegroundInteraction && this.onForegroundInteraction.cancel) {
|
||||
this.onForegroundInteraction.cancel();
|
||||
}
|
||||
if (this.willBlurListener && this.willBlurListener.remove) {
|
||||
this.willBlurListener.remove();
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ const Header = React.memo(({
|
|||
const { isLandscape } = useOrientation();
|
||||
const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1;
|
||||
const titleFontSize = 16 * scale;
|
||||
const subTitleFontSize = 12 * scale;
|
||||
const subTitleFontSize = 14 * scale;
|
||||
|
||||
if (showSearchHeader) {
|
||||
return (
|
||||
|
@ -78,11 +78,11 @@ const Header = React.memo(({
|
|||
<CustomIcon
|
||||
name='chevron-down'
|
||||
color={themes[theme].headerTintColor}
|
||||
style={[showServerDropdown && styles.upsideDown, { fontSize: subTitleFontSize }]}
|
||||
style={[showServerDropdown && styles.upsideDown]}
|
||||
size={18}
|
||||
/>
|
||||
</View>
|
||||
{subtitle ? <Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{subtitle}</Text> : null}
|
||||
{subtitle ? <Text style={[styles.subtitle, { color: themes[theme].auxiliaryText, fontSize: subTitleFontSize }]} numberOfLines={1}>{subtitle}</Text> : null}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
RefreshControl
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { isEqual, orderBy } from 'lodash';
|
||||
import isEqual from 'react-fast-compare';
|
||||
import Orientation from 'react-native-orientation-locker';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
@ -71,6 +71,7 @@ const DISCUSSIONS_HEADER = 'Discussions';
|
|||
const CHANNELS_HEADER = 'Channels';
|
||||
const DM_HEADER = 'Direct_Messages';
|
||||
const GROUPS_HEADER = 'Private_Groups';
|
||||
const QUERY_SIZE = 20;
|
||||
|
||||
const filterIsUnread = s => (s.unread > 0 || s.alert) && !s.hideUnreadStatus;
|
||||
const filterIsFavorite = s => s.f;
|
||||
|
@ -140,11 +141,12 @@ class RoomsListView extends React.Component {
|
|||
|
||||
this.gotSubscriptions = false;
|
||||
this.animated = false;
|
||||
this.count = 0;
|
||||
this.state = {
|
||||
searching: false,
|
||||
search: [],
|
||||
loading: true,
|
||||
allChats: [],
|
||||
chatsOrder: [],
|
||||
chats: [],
|
||||
item: {}
|
||||
};
|
||||
|
@ -211,7 +213,7 @@ class RoomsListView extends React.Component {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { allChats, searching, item } = this.state;
|
||||
const { chatsOrder, searching, item } = this.state;
|
||||
// eslint-disable-next-line react/destructuring-assignment
|
||||
const propsUpdated = shouldUpdateProps.some(key => nextProps[key] !== this.props[key]);
|
||||
if (propsUpdated) {
|
||||
|
@ -219,7 +221,7 @@ class RoomsListView extends React.Component {
|
|||
}
|
||||
|
||||
// Compare changes only once
|
||||
const chatsNotEqual = !isEqual(nextState.allChats, allChats);
|
||||
const chatsNotEqual = !isEqual(nextState.chatsOrder, chatsOrder);
|
||||
|
||||
// If they aren't equal, set to update if focused
|
||||
if (chatsNotEqual) {
|
||||
|
@ -290,7 +292,7 @@ class RoomsListView extends React.Component {
|
|||
&& prevProps.showUnread === showUnread
|
||||
)
|
||||
) {
|
||||
this.getSubscriptions(true);
|
||||
this.getSubscriptions();
|
||||
} else if (
|
||||
appState === 'foreground'
|
||||
&& appState !== prevProps.appState
|
||||
|
@ -309,9 +311,7 @@ class RoomsListView extends React.Component {
|
|||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.querySubscription && this.querySubscription.unsubscribe) {
|
||||
this.querySubscription.unsubscribe();
|
||||
}
|
||||
this.unsubscribeQuery();
|
||||
if (this.unsubscribeFocus) {
|
||||
this.unsubscribeFocus();
|
||||
}
|
||||
|
@ -396,17 +396,8 @@ class RoomsListView extends React.Component {
|
|||
return allData;
|
||||
}
|
||||
|
||||
getSubscriptions = async(force = false) => {
|
||||
if (this.gotSubscriptions && !force) {
|
||||
return;
|
||||
}
|
||||
this.gotSubscriptions = true;
|
||||
|
||||
if (this.querySubscription && this.querySubscription.unsubscribe) {
|
||||
this.querySubscription.unsubscribe();
|
||||
}
|
||||
|
||||
this.setState({ loading: true });
|
||||
getSubscriptions = async() => {
|
||||
this.unsubscribeQuery();
|
||||
|
||||
const {
|
||||
sortBy,
|
||||
|
@ -416,41 +407,49 @@ class RoomsListView extends React.Component {
|
|||
} = this.props;
|
||||
|
||||
const db = database.active;
|
||||
const observable = await db.collections
|
||||
.get('subscriptions')
|
||||
.query(
|
||||
let observable;
|
||||
|
||||
const defaultWhereClause = [
|
||||
Q.where('archived', false),
|
||||
Q.where('open', true)
|
||||
];
|
||||
|
||||
if (sortBy === 'alphabetical') {
|
||||
defaultWhereClause.push(Q.experimentalSortBy(`${ this.useRealName ? 'fname' : 'name' }`, Q.asc));
|
||||
} else {
|
||||
defaultWhereClause.push(Q.experimentalSortBy('room_updated_at', Q.desc));
|
||||
}
|
||||
|
||||
// When we're grouping by something
|
||||
if (this.isGrouping) {
|
||||
observable = await db.collections
|
||||
.get('subscriptions')
|
||||
.query(...defaultWhereClause)
|
||||
.observe();
|
||||
|
||||
// When we're NOT grouping
|
||||
} else {
|
||||
this.count += QUERY_SIZE;
|
||||
observable = await db.collections
|
||||
.get('subscriptions')
|
||||
.query(
|
||||
...defaultWhereClause,
|
||||
Q.experimentalSkip(0),
|
||||
Q.experimentalTake(this.count)
|
||||
)
|
||||
.observeWithColumns(['room_updated_at', 'unread', 'alert', 'user_mentions', 'f', 't']);
|
||||
.observe();
|
||||
}
|
||||
|
||||
|
||||
this.querySubscription = observable.subscribe((data) => {
|
||||
let tempChats = [];
|
||||
let chats = [];
|
||||
if (sortBy === 'alphabetical') {
|
||||
chats = orderBy(data, [`${ this.useRealName ? 'fname' : 'name' }`], ['asc']);
|
||||
} else {
|
||||
chats = orderBy(data, ['roomUpdatedAt'], ['desc']);
|
||||
}
|
||||
let chats = data;
|
||||
|
||||
// it's better to map and test all subs altogether then testing them individually
|
||||
const allChats = data.map(item => ({
|
||||
alert: item.alert,
|
||||
unread: item.unread,
|
||||
userMentions: item.userMentions,
|
||||
isRead: this.getIsRead(item),
|
||||
favorite: item.f,
|
||||
lastMessage: item.lastMessage,
|
||||
name: this.getRoomTitle(item),
|
||||
_updatedAt: item.roomUpdatedAt,
|
||||
key: item._id,
|
||||
rid: item.rid,
|
||||
type: item.t,
|
||||
prid: item.prid,
|
||||
uids: item.uids,
|
||||
usernames: item.usernames,
|
||||
visitor: item.visitor
|
||||
}));
|
||||
/**
|
||||
* We trigger re-render only when chats order changes
|
||||
* RoomItem handles its own re-render
|
||||
*/
|
||||
const chatsOrder = data.map(item => item.rid);
|
||||
|
||||
// unread
|
||||
if (showUnread) {
|
||||
|
@ -484,12 +483,18 @@ class RoomsListView extends React.Component {
|
|||
|
||||
this.internalSetState({
|
||||
chats: tempChats,
|
||||
allChats,
|
||||
chatsOrder,
|
||||
loading: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
unsubscribeQuery = () => {
|
||||
if (this.querySubscription && this.querySubscription.unsubscribe) {
|
||||
this.querySubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
initSearching = () => {
|
||||
const { openSearchHeader } = this.props;
|
||||
this.internalSetState({ searching: true }, () => {
|
||||
|
@ -548,10 +553,19 @@ class RoomsListView extends React.Component {
|
|||
|
||||
getRoomAvatar = item => RocketChat.getRoomAvatar(item)
|
||||
|
||||
isGroupChat = item => RocketChat.isGroupChat(item)
|
||||
|
||||
isRead = item => RocketChat.isRead(item)
|
||||
|
||||
getUserPresence = uid => RocketChat.getUserPresence(uid)
|
||||
|
||||
getUidDirectMessage = room => RocketChat.getUidDirectMessage(room);
|
||||
|
||||
get isGrouping() {
|
||||
const { showUnread, showFavorites, groupByType } = this.props;
|
||||
return showUnread || showFavorites || groupByType;
|
||||
}
|
||||
|
||||
onPressItem = (item = {}) => {
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
if (!navigation.isFocused()) {
|
||||
|
@ -743,6 +757,13 @@ class RoomsListView extends React.Component {
|
|||
roomsRequest({ allData: true });
|
||||
}
|
||||
|
||||
onEndReached = () => {
|
||||
// Run only when we're not grouping by anything
|
||||
if (!this.isGrouping) {
|
||||
this.getSubscriptions();
|
||||
}
|
||||
}
|
||||
|
||||
getScrollRef = ref => (this.scroll = ref);
|
||||
|
||||
renderListHeader = () => {
|
||||
|
@ -774,12 +795,6 @@ class RoomsListView extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
getIsRead = (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;
|
||||
};
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
if (item.separator) {
|
||||
return this.renderSectionHeader(item.rid);
|
||||
|
@ -800,32 +815,19 @@ class RoomsListView extends React.Component {
|
|||
width
|
||||
} = this.props;
|
||||
const id = this.getUidDirectMessage(item);
|
||||
const isGroupChat = RocketChat.isGroupChat(item);
|
||||
|
||||
return (
|
||||
<RoomItem
|
||||
item={item}
|
||||
theme={theme}
|
||||
alert={item.alert}
|
||||
unread={item.unread}
|
||||
hideUnreadStatus={item.hideUnreadStatus}
|
||||
userMentions={item.userMentions}
|
||||
isRead={this.getIsRead(item)}
|
||||
favorite={item.f}
|
||||
avatar={this.getRoomAvatar(item)}
|
||||
lastMessage={item.lastMessage}
|
||||
name={this.getRoomTitle(item)}
|
||||
_updatedAt={item.roomUpdatedAt}
|
||||
key={item._id}
|
||||
id={id}
|
||||
type={item.t}
|
||||
userId={userId}
|
||||
username={username}
|
||||
token={token}
|
||||
rid={item.rid}
|
||||
type={item.t}
|
||||
baseUrl={server}
|
||||
prid={item.prid}
|
||||
showLastMessage={StoreLastMessage}
|
||||
onPress={() => this.onPressItem(item)}
|
||||
onPress={this.onPressItem}
|
||||
testID={`rooms-list-view-item-${ item.name }`}
|
||||
width={isMasterDetail ? MAX_SIDEBAR_WIDTH : width}
|
||||
toggleFav={this.toggleFav}
|
||||
|
@ -833,7 +835,10 @@ class RoomsListView extends React.Component {
|
|||
hideChannel={this.hideChannel}
|
||||
useRealName={useRealName}
|
||||
getUserPresence={this.getUserPresence}
|
||||
isGroupChat={isGroupChat}
|
||||
getRoomTitle={this.getRoomTitle}
|
||||
getRoomAvatar={this.getRoomAvatar}
|
||||
getIsGroupChat={this.isGroupChat}
|
||||
getIsRead={this.isRead}
|
||||
visitor={item.visitor}
|
||||
isFocused={currentItem?.rid === item.rid}
|
||||
/>
|
||||
|
@ -880,6 +885,8 @@ class RoomsListView extends React.Component {
|
|||
/>
|
||||
)}
|
||||
windowSize={9}
|
||||
onEndReached={this.onEndReached}
|
||||
onEndReachedThreshold={0.5}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@ import ShareExtension from 'rn-extensions-share';
|
|||
import * as FileSystem from 'expo-file-system';
|
||||
import { connect } from 'react-redux';
|
||||
import * as mime from 'react-native-mime-types';
|
||||
import { isEqual, orderBy } from 'lodash';
|
||||
import isEqual from 'react-fast-compare';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
||||
import database from '../../lib/database';
|
||||
|
@ -32,7 +32,6 @@ const permission = {
|
|||
message: I18n.t('Read_External_Permission_Message')
|
||||
};
|
||||
|
||||
const LIMIT = 50;
|
||||
const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index });
|
||||
const keyExtractor = item => item.rid;
|
||||
|
||||
|
@ -47,7 +46,7 @@ class ShareListView extends React.Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.data = [];
|
||||
this.chats = [];
|
||||
this.state = {
|
||||
searching: false,
|
||||
searchText: '',
|
||||
|
@ -186,22 +185,36 @@ class ShareListView extends React.Component {
|
|||
this.setState(...args);
|
||||
}
|
||||
|
||||
getSubscriptions = async(server) => {
|
||||
query = (text) => {
|
||||
const db = database.active;
|
||||
const defaultWhereClause = [
|
||||
Q.where('archived', false),
|
||||
Q.where('open', true),
|
||||
Q.experimentalSkip(0),
|
||||
Q.experimentalTake(50),
|
||||
Q.experimentalSortBy('room_updated_at', Q.desc)
|
||||
];
|
||||
if (text) {
|
||||
return db.collections
|
||||
.get('subscriptions')
|
||||
.query(
|
||||
...defaultWhereClause,
|
||||
Q.or(
|
||||
Q.where('name', Q.like(`%${ Q.sanitizeLikeString(text) }%`)),
|
||||
Q.where('fname', Q.like(`%${ Q.sanitizeLikeString(text) }%`))
|
||||
)
|
||||
).fetch();
|
||||
}
|
||||
return db.collections.get('subscriptions').query(...defaultWhereClause).fetch();
|
||||
}
|
||||
|
||||
getSubscriptions = async(server) => {
|
||||
const serversDB = database.servers;
|
||||
|
||||
if (server) {
|
||||
this.data = await db.collections
|
||||
.get('subscriptions')
|
||||
.query(
|
||||
Q.where('archived', false),
|
||||
Q.where('open', true)
|
||||
).fetch();
|
||||
this.data = orderBy(this.data, ['roomUpdatedAt'], ['desc']);
|
||||
|
||||
this.chats = await this.query();
|
||||
const serversCollection = serversDB.collections.get('servers');
|
||||
this.servers = await serversCollection.query().fetch();
|
||||
this.chats = this.data.slice(0, LIMIT);
|
||||
let serverInfo = {};
|
||||
try {
|
||||
serverInfo = await serversCollection.find(server);
|
||||
|
@ -210,8 +223,8 @@ class ShareListView extends React.Component {
|
|||
}
|
||||
|
||||
this.internalSetState({
|
||||
chats: this.chats ? this.chats.slice() : [],
|
||||
servers: this.servers ? this.servers.slice() : [],
|
||||
chats: this.chats ?? [],
|
||||
servers: this.servers ?? [],
|
||||
loading: false,
|
||||
serverInfo
|
||||
});
|
||||
|
@ -253,10 +266,10 @@ class ShareListView extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
search = (text) => {
|
||||
const result = this.data.filter(item => item.name.includes(text)) || [];
|
||||
search = async(text) => {
|
||||
const result = await this.query(text);
|
||||
this.internalSetState({
|
||||
searchResults: result.slice(0, LIMIT),
|
||||
searchResults: result,
|
||||
searchText: text
|
||||
});
|
||||
}
|
||||
|
@ -297,9 +310,26 @@ class ShareListView extends React.Component {
|
|||
}
|
||||
|
||||
renderItem = ({ item }) => {
|
||||
const { serverInfo } = this.state;
|
||||
const { useRealName } = serverInfo;
|
||||
const {
|
||||
userId, token, server, theme
|
||||
} = this.props;
|
||||
let description;
|
||||
switch (item.t) {
|
||||
case 'c':
|
||||
description = item.topic || item.description;
|
||||
break;
|
||||
case 'p':
|
||||
description = item.topic || item.description;
|
||||
break;
|
||||
case 'd':
|
||||
description = useRealName ? item.name : item.fname;
|
||||
break;
|
||||
default:
|
||||
description = item.fname;
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<DirectoryItem
|
||||
user={{
|
||||
|
@ -309,11 +339,7 @@ class ShareListView extends React.Component {
|
|||
title={this.getRoomTitle(item)}
|
||||
baseUrl={server}
|
||||
avatar={RocketChat.getRoomAvatar(item)}
|
||||
description={
|
||||
item.t === 'c'
|
||||
? (item.topic || item.description)
|
||||
: item.fname
|
||||
}
|
||||
description={description}
|
||||
type={item.prid ? 'discussion' : item.t}
|
||||
onPress={() => this.shareMessage(item)}
|
||||
testID={`share-extension-item-${ item.name }`}
|
||||
|
|
|
@ -24,16 +24,16 @@ import SafeAreaView from '../containers/SafeAreaView';
|
|||
|
||||
const STATUS = [{
|
||||
id: 'online',
|
||||
name: I18n.t('Online')
|
||||
name: 'Online'
|
||||
}, {
|
||||
id: 'busy',
|
||||
name: I18n.t('Busy')
|
||||
name: 'Busy'
|
||||
}, {
|
||||
id: 'away',
|
||||
name: I18n.t('Away')
|
||||
name: 'Away'
|
||||
}, {
|
||||
id: 'offline',
|
||||
name: I18n.t('Invisible')
|
||||
name: 'Invisible'
|
||||
}];
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -164,7 +164,7 @@ class StatusView extends React.Component {
|
|||
const { id, name } = item;
|
||||
return (
|
||||
<ListItem
|
||||
title={name}
|
||||
title={I18n.t(name)}
|
||||
onPress={async() => {
|
||||
if (user.status !== item.id) {
|
||||
try {
|
||||
|
|
|
@ -21,28 +21,28 @@ const THEME_GROUP = 'THEME_GROUP';
|
|||
const DARK_GROUP = 'DARK_GROUP';
|
||||
|
||||
const SYSTEM_THEME = {
|
||||
label: I18n.t('Automatic'),
|
||||
label: 'Automatic',
|
||||
value: 'automatic',
|
||||
group: THEME_GROUP
|
||||
};
|
||||
|
||||
const THEMES = [
|
||||
{
|
||||
label: I18n.t('Light'),
|
||||
label: 'Light',
|
||||
value: 'light',
|
||||
group: THEME_GROUP
|
||||
}, {
|
||||
label: I18n.t('Dark'),
|
||||
label: 'Dark',
|
||||
value: 'dark',
|
||||
group: THEME_GROUP
|
||||
}, {
|
||||
label: I18n.t('Dark'),
|
||||
label: 'Dark',
|
||||
value: 'dark',
|
||||
separator: true,
|
||||
header: I18n.t('Dark_level'),
|
||||
header: 'Dark_level',
|
||||
group: DARK_GROUP
|
||||
}, {
|
||||
label: I18n.t('Black'),
|
||||
label: 'Black',
|
||||
value: 'black',
|
||||
group: DARK_GROUP
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ class ThemeView extends React.Component {
|
|||
<>
|
||||
{item.separator || isFirst ? this.renderSectionHeader(item.header) : null}
|
||||
<ListItem
|
||||
title={label}
|
||||
title={I18n.t(label)}
|
||||
onPress={() => this.onClick(item)}
|
||||
testID={`theme-view-${ value }`}
|
||||
right={this.isSelected(item) ? this.renderIcon : null}
|
||||
|
@ -139,12 +139,12 @@ class ThemeView extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderSectionHeader = (header = I18n.t('Theme')) => {
|
||||
renderSectionHeader = (header = 'Theme') => {
|
||||
const { theme } = this.props;
|
||||
return (
|
||||
<>
|
||||
<View style={styles.info}>
|
||||
<Text style={[styles.infoText, { color: themes[theme].infoText }]}>{header}</Text>
|
||||
<Text style={[styles.infoText, { color: themes[theme].infoText }]}>{I18n.t(header)}</Text>
|
||||
</View>
|
||||
{this.renderSeparator()}
|
||||
</>
|
||||
|
@ -169,7 +169,7 @@ class ThemeView extends React.Component {
|
|||
<StatusBar theme={theme} />
|
||||
<FlatList
|
||||
data={THEMES}
|
||||
keyExtractor={item => item.value}
|
||||
keyExtractor={item => item.value + item.group}
|
||||
contentContainerStyle={[
|
||||
styles.list,
|
||||
{ borderColor: themes[theme].separatorColor }
|
||||
|
|
17
e2e/data.js
17
e2e/data.js
|
@ -9,34 +9,39 @@ const data = {
|
|||
regular: {
|
||||
username: `userone${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+regular${ value }@rocket.chat`
|
||||
email: `mobile+regular${ value }@rocket.chat`
|
||||
},
|
||||
alternate: {
|
||||
username: `usertwo${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+alternate${ value }@rocket.chat`,
|
||||
email: `mobile+alternate${ value }@rocket.chat`,
|
||||
totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ'
|
||||
},
|
||||
profileChanges: {
|
||||
username: `userthree${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+profileChanges${ value }@rocket.chat`
|
||||
email: `mobile+profileChanges${ value }@rocket.chat`
|
||||
},
|
||||
existing: {
|
||||
username: `existinguser${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+existing${ value }@rocket.chat`
|
||||
email: `mobile+existing${ value }@rocket.chat`
|
||||
}
|
||||
},
|
||||
channels: {
|
||||
public: {
|
||||
detoxpublic: {
|
||||
name: 'detox-public'
|
||||
}
|
||||
},
|
||||
groups: {
|
||||
private: {
|
||||
name: `detox-private-${ value }`
|
||||
}
|
||||
},
|
||||
registeringUser: {
|
||||
username: `newuser${ value }`,
|
||||
password: `password${ value }`,
|
||||
email: `diego.mello+registering${ value }@rocket.chat`
|
||||
email: `mobile+registering${ value }@rocket.chat`
|
||||
},
|
||||
random: value
|
||||
}
|
||||
|
|
|
@ -9,34 +9,39 @@ const data = {
|
|||
regular: {
|
||||
username: `userone${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+regular${ value }@rocket.chat`
|
||||
email: `mobile+regular${ value }@rocket.chat`
|
||||
},
|
||||
alternate: {
|
||||
username: `usertwo${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+alternate${ value }@rocket.chat`,
|
||||
email: `mobile+alternate${ value }@rocket.chat`,
|
||||
totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ'
|
||||
},
|
||||
profileChanges: {
|
||||
username: `userthree${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+profileChanges${ value }@rocket.chat`
|
||||
email: `mobile+profileChanges${ value }@rocket.chat`
|
||||
},
|
||||
existing: {
|
||||
username: `existinguser${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+existing${ value }@rocket.chat`
|
||||
email: `mobile+existing${ value }@rocket.chat`
|
||||
}
|
||||
},
|
||||
channels: {
|
||||
public: {
|
||||
detoxpublic: {
|
||||
name: 'detox-public'
|
||||
}
|
||||
},
|
||||
groups: {
|
||||
private: {
|
||||
name: `detox-private-${ value }`
|
||||
}
|
||||
},
|
||||
registeringUser: {
|
||||
username: `newuser${ value }`,
|
||||
password: `password${ value }`,
|
||||
email: `diego.mello+registering${ value }@rocket.chat`
|
||||
email: `mobile+registering${ value }@rocket.chat`
|
||||
},
|
||||
random: value
|
||||
}
|
||||
|
|
|
@ -9,34 +9,39 @@ const data = {
|
|||
regular: {
|
||||
username: `userone${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+regular${ value }@rocket.chat`
|
||||
email: `mobile+regular${ value }@rocket.chat`
|
||||
},
|
||||
alternate: {
|
||||
username: `usertwo${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+alternate${ value }@rocket.chat`,
|
||||
email: `mobile+alternate${ value }@rocket.chat`,
|
||||
totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ'
|
||||
},
|
||||
profileChanges: {
|
||||
username: `userthree${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+profileChanges${ value }@rocket.chat`
|
||||
email: `mobile+profileChanges${ value }@rocket.chat`
|
||||
},
|
||||
existing: {
|
||||
username: `existinguser${ value }`,
|
||||
password: '123',
|
||||
email: `diego.mello+existing${ value }@rocket.chat`
|
||||
email: `mobile+existing${ value }@rocket.chat`
|
||||
}
|
||||
},
|
||||
channels: {
|
||||
public: {
|
||||
detoxpublic: {
|
||||
name: 'detox-public'
|
||||
}
|
||||
},
|
||||
groups: {
|
||||
private: {
|
||||
name: `detox-private-${ value }`
|
||||
}
|
||||
},
|
||||
registeringUser: {
|
||||
username: `newuser${ value }`,
|
||||
password: `password${ value }`,
|
||||
email: `diego.mello+registering${ value }@rocket.chat`
|
||||
email: `mobile+registering${ value }@rocket.chat`
|
||||
},
|
||||
random: value
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ if [ "$COMMAND" == "start" ]; then
|
|||
MAX_ATTEMPTS=60
|
||||
while [ $ATTEMPT_NUMBER -lt $MAX_ATTEMPTS ]; do # https://stackoverflow.com/a/21189312/399007
|
||||
ATTEMPT_NUMBER=$((ATTEMPT_NUMBER + 1 ))
|
||||
echo "Waiting for server to be up ($ATTEMPT_NUMBER of $MAX_ATTEMPTS)"
|
||||
echo "Checking if servers are ready (attempt $ATTEMPT_NUMBER of $MAX_ATTEMPTS)"
|
||||
LOGS=$(docker logs rc_test_env_rocketchat_1 2> /dev/null)
|
||||
if grep -q 'SERVER RUNNING' <<< $LOGS ; then
|
||||
echo "RocketChat is ready!"
|
||||
|
|
|
@ -31,7 +31,6 @@ async function login(username, password) {
|
|||
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
|
||||
await element(by.id('login-view-email')).replaceText(username);
|
||||
await element(by.id('login-view-password')).replaceText(password);
|
||||
await sleep(300);
|
||||
await element(by.id('login-view-submit')).tap();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
|
||||
}
|
||||
|
@ -61,6 +60,33 @@ async function mockMessage(message) {
|
|||
await element(by.label(`${ data.random }${ message }`)).atIndex(0).tap();
|
||||
};
|
||||
|
||||
async function starMessage(message){
|
||||
const messageLabel = `${ data.random }${ message }`
|
||||
await waitFor(element(by.label(messageLabel))).toBeVisible().withTimeout(5000);
|
||||
await element(by.label(messageLabel)).atIndex(0).longPress();
|
||||
await expect(element(by.id('action-sheet'))).toExist();
|
||||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
await element(by.label('Star')).tap();
|
||||
await waitFor(element(by.id('action-sheet'))).toNotExist().withTimeout(5000);
|
||||
};
|
||||
|
||||
async function pinMessage(message){
|
||||
const messageLabel = `${ data.random }${ message }`
|
||||
await waitFor(element(by.label(messageLabel)).atIndex(0)).toExist();
|
||||
await element(by.label(messageLabel)).atIndex(0).longPress();
|
||||
await expect(element(by.id('action-sheet'))).toExist();
|
||||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
await element(by.label('Pin')).tap();
|
||||
await waitFor(element(by.id('action-sheet'))).toNotExist().withTimeout(5000);
|
||||
}
|
||||
|
||||
async function dismissReviewNag(){
|
||||
await waitFor(element(by.text('Are you enjoying this app?'))).toExist().withTimeout(60000);
|
||||
await element(by.label('No').and(by.type('_UIAlertControllerActionView'))).tap(); // Tap `no` on ask for review alert
|
||||
}
|
||||
|
||||
async function tapBack() {
|
||||
await element(by.id('header-back')).atIndex(0).tap();
|
||||
}
|
||||
|
@ -74,7 +100,22 @@ async function searchRoom(room) {
|
|||
await expect(element(by.id('rooms-list-view-search-input'))).toExist();
|
||||
await waitFor(element(by.id('rooms-list-view-search-input'))).toExist().withTimeout(5000);
|
||||
await element(by.id('rooms-list-view-search-input')).typeText(room);
|
||||
await sleep(2000);
|
||||
}
|
||||
|
||||
async function tryTapping(theElement, timeout, longtap = false){
|
||||
try {
|
||||
if(longtap){
|
||||
await theElement.longPress()
|
||||
} else {
|
||||
await theElement.tap()
|
||||
}
|
||||
} catch(e) {
|
||||
if(timeout <= 0){ //TODO: Maths. How closely has the timeout been honoured here?
|
||||
throw e
|
||||
}
|
||||
await sleep(100)
|
||||
await tryTapping(theElement, timeout - 100)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -84,7 +125,11 @@ module.exports = {
|
|||
login,
|
||||
logout,
|
||||
mockMessage,
|
||||
starMessage,
|
||||
pinMessage,
|
||||
dismissReviewNag,
|
||||
tapBack,
|
||||
sleep,
|
||||
searchRoom
|
||||
searchRoom,
|
||||
tryTapping
|
||||
};
|
|
@ -38,7 +38,7 @@ const createUser = async (username, password, name, email) => {
|
|||
}
|
||||
|
||||
const createChannelIfNotExists = async (channelname) => {
|
||||
console.log(`Creating channel ${channelname}`)
|
||||
console.log(`Creating public channel ${channelname}`)
|
||||
try {
|
||||
await rocketchat.post('channels.create', {
|
||||
"name": channelname
|
||||
|
@ -49,7 +49,24 @@ const createChannelIfNotExists = async (channelname) => {
|
|||
} catch (infoError) {
|
||||
console.log(JSON.stringify(createError))
|
||||
console.log(JSON.stringify(infoError))
|
||||
throw "Failed to find or create channel"
|
||||
throw "Failed to find or create public channel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createGroupIfNotExists = async (groupname) => {
|
||||
console.log(`Creating private group ${groupname}`)
|
||||
try {
|
||||
await rocketchat.post('groups.create', {
|
||||
"name": groupname
|
||||
})
|
||||
} catch (createError) {
|
||||
try { //Maybe it exists already?
|
||||
await rocketchat.get(`group.info?roomName=${groupname}`)
|
||||
} catch (infoError) {
|
||||
console.log(JSON.stringify(createError))
|
||||
console.log(JSON.stringify(infoError))
|
||||
throw "Failed to find or create private group"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +88,15 @@ const setup = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
await login(data.users.regular.username, data.users.regular.password)
|
||||
|
||||
for (var groupKey in data.groups) {
|
||||
if (data.groups.hasOwnProperty(groupKey)) {
|
||||
const group = data.groups[groupKey]
|
||||
await createGroupIfNotExists(group.name)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -9,12 +9,12 @@ const checkServer = async(server) => {
|
|||
await element(by.id('rooms-list-view-sidebar')).tap();
|
||||
await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000);
|
||||
await waitFor(element(by.label(label))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.label(label))).toBeVisible();
|
||||
await element(by.id('sidebar-close-drawer')).tap();
|
||||
}
|
||||
|
||||
describe('Change server', () => {
|
||||
before(async() => {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await navigateToLogin();
|
||||
await login(data.users.regular.username, data.users.regular.password);
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
|
||||
|
@ -28,8 +28,6 @@ describe('Change server', () => {
|
|||
await sleep(5000);
|
||||
await element(by.id('rooms-list-header-server-dropdown-button')).tap();
|
||||
await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id('rooms-list-header-server-dropdown'))).toExist();
|
||||
await sleep(1000);
|
||||
await element(by.id('rooms-list-header-server-add')).tap();
|
||||
|
||||
// TODO: refactor
|
||||
|
@ -37,19 +35,16 @@ describe('Change server', () => {
|
|||
await element(by.id('new-server-view-input')).replaceText(data.alternateServer);
|
||||
await element(by.id('new-server-view-button')).tap();
|
||||
await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('workspace-view'))).toBeVisible();
|
||||
await element(by.id('workspace-view-register')).tap();
|
||||
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
|
||||
await expect(element(by.id('register-view'))).toBeVisible();
|
||||
|
||||
// Register new user
|
||||
await element(by.id('register-view-name')).replaceText(data.registeringUser.username);
|
||||
await element(by.id('register-view-username')).replaceText(data.registeringUser.username);
|
||||
await element(by.id('register-view-email')).replaceText(data.registeringUser.email);
|
||||
await element(by.id('register-view-password')).replaceText(data.registeringUser.password);
|
||||
await sleep(1000);
|
||||
await element(by.id('register-view-submit')).tap();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('rooms-list-view'))).toBeVisible();
|
||||
|
||||
// For a sanity test, to make sure roomslist is showing correct rooms
|
||||
// app CANNOT show public room created on previous tests
|
||||
|
@ -59,11 +54,8 @@ describe('Change server', () => {
|
|||
});
|
||||
|
||||
it('should change back', async() => {
|
||||
await sleep(5000);
|
||||
await element(by.id('rooms-list-header-server-dropdown-button')).tap();
|
||||
await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id('rooms-list-header-server-dropdown'))).toExist();
|
||||
await sleep(1000);
|
||||
await element(by.id(`rooms-list-header-server-${ data.server }`)).tap();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
|
||||
await checkServer(data.server);
|
||||
|
|
|
@ -23,28 +23,16 @@ describe('Broadcast room', () => {
|
|||
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
|
||||
await element(by.id('select-users-view-search')).replaceText(otheruser.username);
|
||||
await waitFor(element(by.id(`select-users-view-item-${ otheruser.username }`))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id(`select-users-view-item-${ otheruser.username }`))).toBeVisible();
|
||||
await element(by.id(`select-users-view-item-${ otheruser.username }`)).tap();
|
||||
await waitFor(element(by.id(`selected-user-${ otheruser.username }`))).toBeVisible().withTimeout(5000);
|
||||
await sleep(1000);
|
||||
await element(by.id('selected-users-view-submit')).tap();
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(5000);
|
||||
await element(by.id('create-channel-name')).replaceText(`broadcast${ data.random }`);
|
||||
await sleep(2000);
|
||||
await element(by.id('create-channel-broadcast')).tap();
|
||||
if (device.getPlatform() === 'ios') { //Because this tap is FLAKY on iOS
|
||||
await expect(element(by.id('create-channel-broadcast'))).toHaveValue('1')
|
||||
}
|
||||
await sleep(500);
|
||||
await element(by.id('create-channel-broadcast')).longPress(); //https://github.com/facebook/react-native/issues/28032
|
||||
await element(by.id('create-channel-submit')).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('room-view'))).toBeVisible();
|
||||
await waitFor(element(by.id(`room-view-title-broadcast${ data.random }`))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id(`room-view-title-broadcast${ data.random }`))).toBeVisible();
|
||||
await sleep(1000);
|
||||
await element(by.id('room-view-header-actions')).tap();
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
|
||||
await element(by.id('room-actions-info')).tap();
|
||||
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
|
||||
|
@ -64,25 +52,19 @@ describe('Broadcast room', () => {
|
|||
it('should login as user without write message authorization and enter room', async() => {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await navigateToLogin();
|
||||
await element(by.id('login-view-email')).replaceText(otheruser.username);
|
||||
await element(by.id('login-view-password')).replaceText(otheruser.password);
|
||||
await sleep(1000);
|
||||
await element(by.id('login-view-submit')).tap();
|
||||
await login(otheruser.username, otheruser.password);
|
||||
|
||||
//await waitFor(element(by.id('two-factor'))).toBeVisible().withTimeout(5000);
|
||||
//await expect(element(by.id('two-factor'))).toBeVisible();
|
||||
//const code = GA.gen(data.alternateUserTOTPSecret);
|
||||
//await element(by.id('two-factor-input')).replaceText(code);
|
||||
//await sleep(1000);
|
||||
//await element(by.id('two-factor-send')).tap();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
|
||||
|
||||
await searchRoom(`broadcast${ data.random }`);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist();
|
||||
await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||
await waitFor(element(by.id(`room-view-title-broadcast${ data.random }`))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id(`room-view-title-broadcast${ data.random }`))).toBeVisible();
|
||||
await sleep(1000);
|
||||
});
|
||||
|
||||
it('should not have messagebox', async() => {
|
||||
|
@ -95,7 +77,6 @@ describe('Broadcast room', () => {
|
|||
|
||||
it('should have the message created earlier', async() => {
|
||||
await waitFor(element(by.label(`${ data.random }message`)).atIndex(0)).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.label(`${ data.random }message`)).atIndex(0)).toBeVisible();
|
||||
});
|
||||
|
||||
it('should have reply button', async() => {
|
||||
|
@ -104,9 +85,7 @@ describe('Broadcast room', () => {
|
|||
|
||||
it('should tap on reply button and navigate to direct room', async() => {
|
||||
await element(by.id('message-broadcast-reply')).tap();
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.id(`room-view-title-${ testuser.username }`))).toBeVisible().withTimeout(5000);
|
||||
await expect(element(by.id(`room-view-title-${ testuser.username }`))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should reply broadcasted message', async() => {
|
||||
|
|
|
@ -13,7 +13,7 @@ async function waitForToast() {
|
|||
// await expect(element(by.id('toast'))).toBeVisible();
|
||||
// await waitFor(element(by.id('toast'))).toBeNotVisible().withTimeout(10000);
|
||||
// await expect(element(by.id('toast'))).toBeNotVisible();
|
||||
await sleep(5000);
|
||||
await sleep(1);
|
||||
}
|
||||
|
||||
describe('Profile screen', () => {
|
||||
|
@ -24,7 +24,6 @@ describe('Profile screen', () => {
|
|||
await element(by.id('rooms-list-view-sidebar')).tap();
|
||||
await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000);
|
||||
await waitFor(element(by.id('sidebar-profile'))).toBeVisible().withTimeout(2000);
|
||||
await expect(element(by.id('sidebar-profile'))).toBeVisible();
|
||||
await element(by.id('sidebar-profile')).tap();
|
||||
await waitFor(element(by.id('profile-view'))).toBeVisible().withTimeout(2000);
|
||||
});
|
||||
|
@ -60,22 +59,18 @@ describe('Profile screen', () => {
|
|||
|
||||
it('should have reset avatar button', async() => {
|
||||
await waitFor(element(by.id('profile-view-reset-avatar'))).toExist().whileElement(by.id('profile-view-list')).scroll(scrollDown, 'down');
|
||||
await expect(element(by.id('profile-view-reset-avatar'))).toExist();
|
||||
});
|
||||
|
||||
it('should have upload avatar button', async() => {
|
||||
await waitFor(element(by.id('profile-view-upload-avatar'))).toExist().whileElement(by.id('profile-view-list')).scroll(scrollDown, 'down');
|
||||
await expect(element(by.id('profile-view-upload-avatar'))).toExist();
|
||||
});
|
||||
|
||||
it('should have avatar url button', async() => {
|
||||
await waitFor(element(by.id('profile-view-avatar-url-button'))).toExist().whileElement(by.id('profile-view-list')).scroll(scrollDown, 'down');
|
||||
await expect(element(by.id('profile-view-avatar-url-button'))).toExist();
|
||||
});
|
||||
|
||||
it('should have submit button', async() => {
|
||||
await waitFor(element(by.id('profile-view-submit'))).toExist().whileElement(by.id('profile-view-list')).scroll(scrollDown, 'down');
|
||||
await expect(element(by.id('profile-view-submit'))).toExist();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -84,15 +79,13 @@ describe('Profile screen', () => {
|
|||
await element(by.type('UIScrollView')).atIndex(1).swipe('down');
|
||||
await element(by.id('profile-view-name')).replaceText(`${ profileChangeUser.username }new`);
|
||||
await element(by.id('profile-view-username')).replaceText(`${ profileChangeUser.username }new`);
|
||||
await sleep(1000);
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await sleep(1000);
|
||||
await element(by.id('profile-view-submit')).tap();
|
||||
await waitForToast();
|
||||
});
|
||||
|
||||
it('should change email and password', async() => {
|
||||
await element(by.id('profile-view-email')).replaceText(`diego.mello+profileChangesNew${ data.random }@rocket.chat`);
|
||||
await element(by.id('profile-view-email')).replaceText(`mobile+profileChangesNew${ data.random }@rocket.chat`);
|
||||
await element(by.id('profile-view-new-password')).replaceText(`${ profileChangeUser.password }new`);
|
||||
await element(by.id('profile-view-submit')).tap();
|
||||
await element(by.type('_UIAlertControllerTextField')).replaceText(`${ profileChangeUser.password }`)
|
||||
|
@ -103,7 +96,6 @@ describe('Profile screen', () => {
|
|||
|
||||
it('should reset avatar', async() => {
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await sleep(1000);
|
||||
await element(by.id('profile-view-reset-avatar')).tap();
|
||||
await waitForToast();
|
||||
});
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
const {
|
||||
device, expect, element, by, waitFor
|
||||
} = require('detox');
|
||||
const { navigateToLogin, login } = require('../../helpers/app');
|
||||
|
||||
const data = require('../../data');
|
||||
|
||||
const testuser = data.users.regular
|
||||
|
||||
describe('Settings screen', () => {
|
||||
before(async() => {
|
||||
await device.launchApp({ newInstance: true });
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await navigateToLogin();
|
||||
await login(testuser.username, testuser.password);
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
|
||||
await expect(element(by.id('rooms-list-view'))).toBeVisible();
|
||||
await element(by.id('rooms-list-view-sidebar')).tap();
|
||||
await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000);
|
||||
await waitFor(element(by.id('sidebar-settings'))).toBeVisible().withTimeout(2000);
|
||||
|
|
|
@ -2,12 +2,12 @@ const {
|
|||
device, expect, element, by, waitFor
|
||||
} = require('detox');
|
||||
const data = require('../../data');
|
||||
const { mockMessage, tapBack, sleep, searchRoom } = require('../../helpers/app');
|
||||
const { navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom } = require('../../helpers/app');
|
||||
|
||||
const room = 'detox-public';
|
||||
const testuser = data.users.regular
|
||||
const room = data.channels.detoxpublic.name;
|
||||
|
||||
async function navigateToRoom() {
|
||||
await sleep(2000);
|
||||
await searchRoom(room);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0)).toBeVisible().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0).tap();
|
||||
|
@ -15,15 +15,15 @@ async function navigateToRoom() {
|
|||
}
|
||||
|
||||
async function navigateToRoomActions() {
|
||||
await sleep(2000);
|
||||
await element(by.id('room-view-header-actions')).tap();
|
||||
await sleep(2000);
|
||||
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
|
||||
}
|
||||
|
||||
describe('Join public room', () => {
|
||||
before(async() => {
|
||||
await device.launchApp({ newInstance: true });
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await navigateToLogin();
|
||||
await login(testuser.username, testuser.password);
|
||||
await navigateToRoom();
|
||||
});
|
||||
|
||||
|
@ -167,9 +167,7 @@ describe('Join public room', () => {
|
|||
await element(by.text('Yes, leave it!')).tap();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
|
||||
// await element(by.id('rooms-list-view-search')).typeText('');
|
||||
await sleep(2000);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible().withTimeout(60000);
|
||||
await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
const {
|
||||
expect, element, by, waitFor
|
||||
} = require('detox');
|
||||
const { sleep } = require('../../helpers/app');
|
||||
const { navigateToLogin, login, sleep } = require('../../helpers/app');
|
||||
|
||||
const data = require('../../data');
|
||||
const testuser = data.users.regular
|
||||
|
||||
async function waitForToast() {
|
||||
await sleep(5000);
|
||||
await sleep(1);
|
||||
}
|
||||
|
||||
describe('Status screen', () => {
|
||||
before(async () => {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await navigateToLogin();
|
||||
await login(testuser.username, testuser.password);
|
||||
|
||||
await element(by.id('rooms-list-view-sidebar')).tap();
|
||||
await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000);
|
||||
await waitFor(element(by.id('sidebar-custom-status'))).toBeVisible().withTimeout(2000);
|
||||
|
@ -29,15 +36,12 @@ describe('Status screen', () => {
|
|||
|
||||
describe('Usage', async () => {
|
||||
it('should change status', async () => {
|
||||
await sleep(1000);
|
||||
await element(by.id('status-view-busy')).tap();
|
||||
await sleep(1000);
|
||||
await expect(element(by.id('status-view-current-busy'))).toExist();
|
||||
});
|
||||
|
||||
it('should change status text', async () => {
|
||||
await element(by.id('status-view-input')).replaceText('status-text-new');
|
||||
await sleep(1000);
|
||||
await element(by.id('status-view-submit')).tap();
|
||||
await waitForToast();
|
||||
await waitFor(element(by.label('status-text-new').withAncestor(by.id('sidebar-custom-status')))).toBeVisible().withTimeout(2000);
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
const detox = require('detox');
|
||||
const config = require('../../package.json').detox;
|
||||
const dataSetup = require('../helpers/data_setup')
|
||||
const adapter = require('detox/runners/mocha/adapter');
|
||||
|
||||
before(async() => {
|
||||
await dataSetup()
|
||||
await detox.init(config, { launchApp: false });
|
||||
await device.launchApp({ permissions: { notifications: 'YES' } });
|
||||
await Promise.all([dataSetup(), detox.init(config, { launchApp: false })])
|
||||
//await dataSetup()
|
||||
//await detox.init(config, { launchApp: false });
|
||||
//await device.launchApp({ permissions: { notifications: 'YES' } });
|
||||
});
|
||||
|
||||
beforeEach(async function() {
|
||||
await adapter.beforeEach(this);
|
||||
});
|
||||
|
||||
afterEach(async function() {
|
||||
await adapter.afterEach(this);
|
||||
});
|
||||
|
||||
after(async() => {
|
||||
|
|
|
@ -31,7 +31,6 @@ describe('Onboarding', () => {
|
|||
it('should navigate to join a workspace', async() => {
|
||||
await element(by.id('join-workspace')).tap();
|
||||
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('new-server-view'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should enter an invalid server and get error', async() => {
|
||||
|
@ -39,14 +38,12 @@ describe('Onboarding', () => {
|
|||
await element(by.id('new-server-view-button')).tap();
|
||||
const errorText = 'Oops!';
|
||||
await waitFor(element(by.text(errorText))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.text(errorText))).toBeVisible();
|
||||
await element(by.text('OK')).tap();
|
||||
});
|
||||
|
||||
it('should tap on "Join our open workspace" and navigate', async() => {
|
||||
await element(by.id('new-server-view-open')).tap();
|
||||
await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('workspace-view'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should enter a valid server without login services and navigate to login', async() => {
|
||||
|
@ -57,7 +54,6 @@ describe('Onboarding', () => {
|
|||
await element(by.id('new-server-view-input')).replaceText(data.server);
|
||||
await element(by.id('new-server-view-button')).tap();
|
||||
await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('workspace-view'))).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,32 +4,38 @@ const {
|
|||
const { navigateToRegister, navigateToLogin } = require('../../helpers/app');
|
||||
|
||||
describe('Legal screen', () => {
|
||||
it('should have legal button on login', async() => {
|
||||
await device.launchApp({ newInstance: true });
|
||||
|
||||
describe('From Login', () => {
|
||||
before(async() => {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await navigateToLogin();
|
||||
});
|
||||
|
||||
it('should have legal button on login', async() => {
|
||||
await waitFor(element(by.id('login-view-more'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('login-view-more'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should navigate to legal from login', async() => {
|
||||
await waitFor(element(by.id('login-view-more'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('login-view-more'))).toBeVisible();
|
||||
await element(by.id('login-view-more')).tap();
|
||||
await waitFor(element(by.id('legal-view'))).toBeVisible().withTimeout(4000)
|
||||
});
|
||||
});
|
||||
|
||||
describe('From Register', () => {
|
||||
before(async() => {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await navigateToRegister();
|
||||
});
|
||||
|
||||
it('should have legal button on register', async() => {
|
||||
await device.launchApp({ newInstance: true });
|
||||
await navigateToRegister();
|
||||
await waitFor(element(by.id('register-view-more'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('register-view-more'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should navigate to legal from register', async() => {
|
||||
await waitFor(element(by.id('register-view-more'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('register-view-more'))).toBeVisible();
|
||||
await element(by.id('register-view-more')).tap();
|
||||
});
|
||||
|
||||
it('should have legal screen', async() => {
|
||||
await expect(element(by.id('legal-view'))).toBeVisible();
|
||||
await waitFor(element(by.id('legal-view'))).toBeVisible().withTimeout(4000);
|
||||
});
|
||||
|
||||
it('should have terms of service button', async() => {
|
||||
|
@ -40,18 +46,20 @@ describe('Legal screen', () => {
|
|||
await expect(element(by.id('legal-privacy-button'))).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
// We can't simulate how webview behaves, so I had to disable :(
|
||||
// it('should navigate to terms', async() => {
|
||||
// await element(by.id('legal-terms-button')).tap();
|
||||
// await waitFor(element(by.id('terms-view'))).toBeVisible().withTimeout(2000);
|
||||
// await expect(element(by.id('terms-view'))).toBeVisible();
|
||||
// });
|
||||
|
||||
// it('should navigate to privacy', async() => {
|
||||
// await tapBack();
|
||||
// await element(by.id('legal-privacy-button')).tap();
|
||||
// await waitFor(element(by.id('privacy-view'))).toBeVisible().withTimeout(2000);
|
||||
// await expect(element(by.id('privacy-view'))).toBeVisible();
|
||||
// });
|
||||
/*
|
||||
it('should navigate to terms', async() => {
|
||||
await element(by.id('legal-terms-button')).tap();
|
||||
await waitFor(element(by.id('terms-view'))).toBeVisible().withTimeout(2000);
|
||||
await expect(element(by.id('terms-view'))).toBeVisible();
|
||||
});
|
||||
|
||||
it('should navigate to privacy', async() => {
|
||||
await tapBack();
|
||||
await element(by.id('legal-privacy-button')).tap();
|
||||
await waitFor(element(by.id('privacy-view'))).toBeVisible().withTimeout(2000);
|
||||
await expect(element(by.id('privacy-view'))).toBeVisible();
|
||||
});
|
||||
*/
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,7 +32,6 @@ describe('Forgot password screen', () => {
|
|||
await element(by.id('forgot-password-view-submit')).tap();
|
||||
await element(by.text('OK')).tap();
|
||||
await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('login-view'))).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -53,10 +53,8 @@ describe('Create user screen', () => {
|
|||
await element(by.id('register-view-username')).replaceText(data.registeringUser.username);
|
||||
await element(by.id('register-view-email')).replaceText(data.users.existing.email);
|
||||
await element(by.id('register-view-password')).replaceText(data.registeringUser.password);
|
||||
await sleep(300);
|
||||
await element(by.id('register-view-submit')).tap();
|
||||
await waitFor(element(by.text('Email already exists. [403]')).atIndex(0)).toExist().withTimeout(10000);
|
||||
await expect(element(by.text('Email already exists. [403]')).atIndex(0)).toExist();
|
||||
await element(by.text('OK')).tap();
|
||||
});
|
||||
|
||||
|
@ -65,10 +63,8 @@ describe('Create user screen', () => {
|
|||
await element(by.id('register-view-username')).replaceText(data.users.existing.username);
|
||||
await element(by.id('register-view-email')).replaceText(data.registeringUser.email);
|
||||
await element(by.id('register-view-password')).replaceText(data.registeringUser.password);
|
||||
await sleep(300);
|
||||
await element(by.id('register-view-submit')).tap();
|
||||
await waitFor(element(by.text('Username is already in use')).atIndex(0)).toExist().withTimeout(10000);
|
||||
await expect(element(by.text('Username is already in use')).atIndex(0)).toExist();
|
||||
await element(by.text('OK')).tap();
|
||||
});
|
||||
|
||||
|
@ -77,10 +73,8 @@ describe('Create user screen', () => {
|
|||
await element(by.id('register-view-username')).replaceText(data.registeringUser.username);
|
||||
await element(by.id('register-view-email')).replaceText(data.registeringUser.email);
|
||||
await element(by.id('register-view-password')).replaceText(data.registeringUser.password);
|
||||
await sleep(300);
|
||||
await element(by.id('register-view-submit')).tap();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('rooms-list-view'))).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -44,33 +44,27 @@ describe('Login screen', () => {
|
|||
it('should navigate to register', async() => {
|
||||
await element(by.id('login-view-register')).tap();
|
||||
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
|
||||
await expect(element(by.id('register-view'))).toBeVisible();
|
||||
await tapBack();
|
||||
});
|
||||
|
||||
it('should navigate to forgot password', async() => {
|
||||
await element(by.id('login-view-forgot-password')).tap();
|
||||
await waitFor(element(by.id('forgot-password-view'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('forgot-password-view'))).toExist();
|
||||
await tapBack();
|
||||
});
|
||||
|
||||
it('should insert wrong password and get error', async() => {
|
||||
await element(by.id('login-view-email')).replaceText(data.users.regular.username);
|
||||
await element(by.id('login-view-password')).replaceText('NotMyActualPassword');
|
||||
await sleep(300);
|
||||
await element(by.id('login-view-submit')).tap();
|
||||
await waitFor(element(by.text('Your credentials were rejected! Please try again.'))).toBeVisible().withTimeout(10000);
|
||||
await expect(element(by.text('Your credentials were rejected! Please try again.'))).toBeVisible();
|
||||
await element(by.text('OK')).tap();
|
||||
});
|
||||
|
||||
it('should login with success', async() => {
|
||||
await element(by.id('login-view-password')).replaceText(data.users.regular.password);
|
||||
await sleep(300);
|
||||
await element(by.id('login-view-submit')).tap();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('rooms-list-view'))).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
const {
|
||||
device, expect, element, by, waitFor
|
||||
} = require('detox');
|
||||
const { logout, tapBack, sleep, searchRoom } = require('../../helpers/app');
|
||||
const { login, navigateToLogin, logout, tapBack, sleep, searchRoom } = require('../../helpers/app');
|
||||
const data = require('../../data');
|
||||
|
||||
describe('Rooms list screen', () => {
|
||||
|
||||
before(async() => {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true });
|
||||
await navigateToLogin();
|
||||
await login(data.users.regular.username, data.users.regular.password)
|
||||
});
|
||||
|
||||
describe('Render', () => {
|
||||
it('should have rooms list screen', async() => {
|
||||
await expect(element(by.id('rooms-list-view'))).toBeVisible();
|
||||
|
@ -29,18 +37,12 @@ describe('Rooms list screen', () => {
|
|||
it('should search room and navigate', async() => {
|
||||
await searchRoom('rocket.cat');
|
||||
await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible();
|
||||
await element(by.id('rooms-list-view-item-rocket.cat')).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(10000);
|
||||
await expect(element(by.id('room-view'))).toBeVisible();
|
||||
await waitFor(element(by.id('room-view-title-rocket.cat'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('room-view-title-rocket.cat'))).toBeVisible();
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||
await expect(element(by.id('rooms-list-view'))).toBeVisible();
|
||||
await sleep(2000);
|
||||
await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toExist();
|
||||
});
|
||||
|
||||
it('should logout', async() => {
|
||||
|
|
|
@ -2,48 +2,50 @@ const {
|
|||
device, expect, element, by, waitFor
|
||||
} = require('detox');
|
||||
const data = require('../../data');
|
||||
const { tapBack, sleep, navigateToLogin, login } = require('../../helpers/app');
|
||||
const { tapBack, sleep, navigateToLogin, login, tryTapping } = require('../../helpers/app');
|
||||
|
||||
|
||||
|
||||
describe('Create room screen', () => {
|
||||
before(async() => {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await navigateToLogin();
|
||||
await login(data.users.regular.username, data.users.regular.password);
|
||||
await element(by.id('rooms-list-view-create-channel')).tap();
|
||||
await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(2000);
|
||||
});
|
||||
|
||||
describe('New Message', async() => {
|
||||
before(async() => {
|
||||
await element(by.id('rooms-list-view-create-channel')).tap();
|
||||
});
|
||||
|
||||
describe('Render', async() => {
|
||||
it('should have new message screen', async() => {
|
||||
await expect(element(by.id('new-message-view'))).toExist();
|
||||
await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000);
|
||||
});
|
||||
|
||||
it('should have search input', async() => {
|
||||
await waitFor(element(by.id('new-message-view-search'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('new-message-view-search'))).toExist();
|
||||
await waitFor(element(by.id('new-message-view-search'))).toBeVisible().withTimeout(2000);
|
||||
});
|
||||
})
|
||||
|
||||
describe('Usage', async() => {
|
||||
it('should back to rooms list', async() => {
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.id('new-message-view-close'))).toBeVisible().withTimeout(2000);
|
||||
await element(by.id('new-message-view-close')).tap();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('rooms-list-view'))).toExist();
|
||||
await element(by.id('rooms-list-view-create-channel')).tap();
|
||||
|
||||
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
|
||||
|
||||
await tryTapping(element(by.id('rooms-list-view-create-channel')), 3000);
|
||||
//await element(by.id('rooms-list-view-create-channel')).tap();
|
||||
await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('new-message-view'))).toExist();
|
||||
});
|
||||
|
||||
it('should search user and navigate', async() => {
|
||||
await element(by.id('new-message-view-search')).replaceText('rocket.cat');
|
||||
await waitFor(element(by.id('new-message-view-item-rocket.cat'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('new-message-view-item-rocket.cat'))).toExist();
|
||||
await element(by.id('new-message-view-item-rocket.cat')).tap();
|
||||
await waitFor(element(by.id('room-view'))).toExist().withTimeout(10000);
|
||||
await expect(element(by.id('room-view'))).toExist();
|
||||
await waitFor(element(by.id('room-view-title-rocket.cat'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('room-view-title-rocket.cat'))).toExist();
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000);
|
||||
});
|
||||
|
@ -51,11 +53,8 @@ describe('Create room screen', () => {
|
|||
it('should navigate to select users', async() => {
|
||||
await element(by.id('rooms-list-view-create-channel')).tap();
|
||||
await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('new-message-view'))).toExist();
|
||||
await sleep(1000);
|
||||
await element(by.id('new-message-view-create-channel')).tap();
|
||||
await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('select-users-view'))).toExist();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
@ -108,7 +107,6 @@ describe('Create room screen', () => {
|
|||
const room = `public${ data.random }`;
|
||||
await element(by.id('create-channel-name')).replaceText(room);
|
||||
await element(by.id('create-channel-type')).tap();
|
||||
await sleep(1000);
|
||||
await element(by.id('create-channel-submit')).tap();
|
||||
await waitFor(element(by.id('room-view'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('room-view'))).toExist();
|
||||
|
@ -123,20 +121,15 @@ describe('Create room screen', () => {
|
|||
it('should create private room', async() => {
|
||||
const room = `private${ data.random }`;
|
||||
await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000);
|
||||
// await device.launchApp({ newInstance: true });
|
||||
await sleep(1000);
|
||||
await element(by.id('rooms-list-view-create-channel')).tap();
|
||||
await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await element(by.id('new-message-view-create-channel')).tap();
|
||||
await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await element(by.id('select-users-view-item-rocket.cat')).tap();
|
||||
await waitFor(element(by.id('selected-user-rocket.cat'))).toExist().withTimeout(5000);
|
||||
await element(by.id('selected-users-view-submit')).tap();
|
||||
await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(5000);
|
||||
await element(by.id('create-channel-name')).replaceText(room);
|
||||
await sleep(1000);
|
||||
await element(by.id('create-channel-submit')).tap();
|
||||
await waitFor(element(by.id('room-view'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('room-view'))).toExist();
|
||||
|
@ -152,17 +145,13 @@ describe('Create room screen', () => {
|
|||
const room = `empty${ data.random }`;
|
||||
await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000);
|
||||
// await device.launchApp({ newInstance: true });
|
||||
await sleep(1000);
|
||||
await element(by.id('rooms-list-view-create-channel')).tap();
|
||||
await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await element(by.id('new-message-view-create-channel')).tap();
|
||||
await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await element(by.id('selected-users-view-submit')).tap();
|
||||
await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(5000);
|
||||
await element(by.id('create-channel-name')).replaceText(room);
|
||||
await sleep(1000);
|
||||
await element(by.id('create-channel-submit')).tap();
|
||||
await waitFor(element(by.id('room-view'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('room-view'))).toExist();
|
||||
|
|
|
@ -2,27 +2,29 @@ const {
|
|||
device, expect, element, by, waitFor
|
||||
} = require('detox');
|
||||
const data = require('../../data');
|
||||
const { mockMessage, tapBack, sleep, searchRoom } = require('../../helpers/app');
|
||||
const { navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom, starMessage, pinMessage, dismissReviewNag, tryTapping } = require('../../helpers/app');
|
||||
|
||||
async function navigateToRoom() {
|
||||
await searchRoom(`private${ data.random }`);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toExist().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-private${ data.random }`)).tap();
|
||||
async function navigateToRoom(roomName) {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await navigateToLogin();
|
||||
await login(data.users.regular.username, data.users.regular.password);
|
||||
await searchRoom(`${ roomName }`);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ roomName }`))).toExist().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ roomName }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||
}
|
||||
|
||||
describe('Room screen', () => {
|
||||
const mainRoom = `private${ data.random }`;
|
||||
const mainRoom = data.groups.private.name;
|
||||
|
||||
before(async() => {
|
||||
await navigateToRoom();
|
||||
await navigateToRoom(mainRoom);
|
||||
});
|
||||
|
||||
describe('Render', async() => {
|
||||
it('should have room screen', async() => {
|
||||
await expect(element(by.id('room-view'))).toExist();
|
||||
await waitFor(element(by.id(`room-view-title-${ mainRoom }`))).toExist().withTimeout(5000);
|
||||
await expect(element(by.id(`room-view-title-${ mainRoom }`))).toExist();
|
||||
});
|
||||
|
||||
// Render - Header
|
||||
|
@ -69,22 +71,15 @@ describe('Room screen', () => {
|
|||
await expect(element(by.label(`${ data.random }message`)).atIndex(0)).toExist();
|
||||
});
|
||||
|
||||
it('should ask for review', async() => {
|
||||
await waitFor(element(by.text('Are you enjoying this app?'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.text('Are you enjoying this app?')).atIndex(0)).toExist();
|
||||
await element(by.label('No').and(by.type('_UIAlertControllerActionView'))).tap(); // Tap `no` on ask for review alert
|
||||
})
|
||||
|
||||
it('should show/hide emoji keyboard', async () => {
|
||||
if (device.getPlatform() === 'android') {
|
||||
await element(by.id('messagebox-open-emoji')).tap();
|
||||
await waitFor(element(by.id('messagebox-keyboard-emoji'))).toExist().withTimeout(10000);
|
||||
await expect(element(by.id('messagebox-keyboard-emoji'))).toExist();
|
||||
await expect(element(by.id('messagebox-close-emoji'))).toExist();
|
||||
await expect(element(by.id('messagebox-open-emoji'))).toBeNotVisible();
|
||||
await element(by.id('messagebox-close-emoji')).tap();
|
||||
await waitFor(element(by.id('messagebox-keyboard-emoji'))).toBeNotVisible().withTimeout(10000);
|
||||
await expect(element(by.id('messagebox-keyboard-emoji'))).toBeNotVisible();
|
||||
await expect(element(by.id('messagebox-close-emoji'))).toBeNotVisible();
|
||||
await expect(element(by.id('messagebox-open-emoji'))).toExist();
|
||||
}
|
||||
|
@ -94,10 +89,8 @@ describe('Room screen', () => {
|
|||
await element(by.id('messagebox-input')).tap();
|
||||
await element(by.id('messagebox-input')).typeText(':joy');
|
||||
await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(10000);
|
||||
await expect(element(by.id('messagebox-container'))).toExist();
|
||||
await element(by.id('messagebox-input')).clearText();
|
||||
await waitFor(element(by.id('messagebox-container'))).toBeNotVisible().withTimeout(10000);
|
||||
await expect(element(by.id('messagebox-container'))).toBeNotVisible();
|
||||
});
|
||||
|
||||
it('should show and tap on emoji autocomplete', async() => {
|
||||
|
@ -105,8 +98,6 @@ describe('Room screen', () => {
|
|||
await element(by.id('messagebox-input')).replaceText(':');
|
||||
await element(by.id('messagebox-input')).typeText('joy'); // workaround for number keyboard
|
||||
await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(10000);
|
||||
await expect(element(by.id('messagebox-container'))).toExist();
|
||||
await sleep(1000);
|
||||
await element(by.id('mention-item-joy')).tap();
|
||||
await expect(element(by.id('messagebox-input'))).toHaveText(':joy: ');
|
||||
await element(by.id('messagebox-input')).clearText();
|
||||
|
@ -116,25 +107,22 @@ describe('Room screen', () => {
|
|||
const username = data.users.regular.username
|
||||
await element(by.id('messagebox-input')).tap();
|
||||
await element(by.id('messagebox-input')).typeText(`@${ username }`);
|
||||
await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('messagebox-container'))).toExist();
|
||||
await sleep(1000);
|
||||
await element(by.id(`mention-item-${ username }`)).tap();
|
||||
await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(4000);
|
||||
await waitFor(element(by.id(`mention-item-${ username }`))).toBeVisible().withTimeout(4000)
|
||||
await tryTapping(element(by.id(`mention-item-${ username }`)), 2000, true);
|
||||
await expect(element(by.id('messagebox-input'))).toHaveText(`@${ username } `);
|
||||
await element(by.id('messagebox-input')).tap();
|
||||
await tryTapping(element(by.id('messagebox-input')), 2000)
|
||||
await element(by.id('messagebox-input')).typeText(`${ data.random }mention`);
|
||||
await element(by.id('messagebox-send-message')).tap();
|
||||
// await waitFor(element(by.label(`@${ data.user } ${ data.random }mention`)).atIndex(0)).toExist().withTimeout(60000);
|
||||
await sleep(2000);
|
||||
});
|
||||
|
||||
it('should show and tap on room autocomplete', async() => {
|
||||
await element(by.id('messagebox-input')).tap();
|
||||
await element(by.id('messagebox-input')).typeText('#general');
|
||||
await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('messagebox-container'))).toExist();
|
||||
await sleep(1000);
|
||||
await element(by.id('mention-item-general')).tap();
|
||||
//await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(4000);
|
||||
await waitFor(element(by.id('mention-item-general'))).toBeVisible().withTimeout(4000);
|
||||
await tryTapping(element(by.id('mention-item-general')), 2000, true)
|
||||
await expect(element(by.id('messagebox-input'))).toHaveText('#general ');
|
||||
await element(by.id('messagebox-input')).clearText();
|
||||
});
|
||||
|
@ -147,7 +135,6 @@ describe('Room screen', () => {
|
|||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
await element(by.label('Permalink')).tap();
|
||||
await sleep(1000);
|
||||
|
||||
// TODO: test clipboard
|
||||
});
|
||||
|
@ -158,28 +145,20 @@ describe('Room screen', () => {
|
|||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
await element(by.label('Copy')).tap();
|
||||
await sleep(1000);
|
||||
|
||||
// TODO: test clipboard
|
||||
});
|
||||
|
||||
it('should star message', async() => {
|
||||
await element(by.label(`${ data.random }message`)).atIndex(0).longPress();
|
||||
await expect(element(by.id('action-sheet'))).toExist();
|
||||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
await element(by.label('Star')).tap();
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.id('action-sheet'))).toNotExist().withTimeout(5000);
|
||||
await starMessage('message')
|
||||
|
||||
await sleep(1000) //https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/2324
|
||||
await element(by.label(`${ data.random }message`)).atIndex(0).longPress();
|
||||
await expect(element(by.id('action-sheet'))).toExist();
|
||||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
await waitFor(element(by.label('Unstar'))).toBeVisible().withTimeout(2000);
|
||||
await expect(element(by.label('Unstar'))).toBeVisible();
|
||||
await element(by.id('action-sheet-backdrop')).tap();
|
||||
await sleep(1000);
|
||||
});
|
||||
|
||||
it('should react to message', async() => {
|
||||
|
@ -189,14 +168,10 @@ describe('Room screen', () => {
|
|||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
await element(by.id('add-reaction')).tap();
|
||||
await waitFor(element(by.id('reaction-picker'))).toBeVisible().withTimeout(2000);
|
||||
await expect(element(by.id('reaction-picker'))).toBeVisible();
|
||||
await element(by.id('reaction-picker-😃')).tap();
|
||||
await waitFor(element(by.id('reaction-picker-grinning'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('reaction-picker-grinning'))).toExist();
|
||||
await element(by.id('reaction-picker-grinning')).tap();
|
||||
await waitFor(element(by.id('message-reaction-:grinning:'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('message-reaction-:grinning:'))).toExist();
|
||||
await sleep(1000);
|
||||
});
|
||||
|
||||
it('should react to message with frequently used emoji', async() => {
|
||||
|
@ -205,30 +180,27 @@ describe('Room screen', () => {
|
|||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
await waitFor(element(by.id('message-actions-emoji-+1'))).toBeVisible().withTimeout(2000);
|
||||
await expect(element(by.id('message-actions-emoji-+1'))).toBeVisible();
|
||||
await element(by.id('message-actions-emoji-+1')).tap();
|
||||
await waitFor(element(by.id('message-reaction-:+1:'))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.id('message-reaction-:+1:'))).toBeVisible();
|
||||
await sleep(1000);
|
||||
});
|
||||
|
||||
it('should show reaction picker on add reaction button pressed and have frequently used emoji', async() => {
|
||||
await element(by.id('message-add-reaction')).tap();
|
||||
await waitFor(element(by.id('reaction-picker'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('reaction-picker'))).toExist();
|
||||
await waitFor(element(by.id('reaction-picker-grinning'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('reaction-picker-grinning'))).toExist();
|
||||
await element(by.id('reaction-picker-😃')).tap();
|
||||
await waitFor(element(by.id('reaction-picker-grimacing'))).toExist().withTimeout(2000);
|
||||
await element(by.id('reaction-picker-grimacing')).tap();
|
||||
await waitFor(element(by.id('message-reaction-:grimacing:'))).toExist().withTimeout(60000);
|
||||
await sleep(1000);
|
||||
});
|
||||
|
||||
it('should ask for review', async() => {
|
||||
await dismissReviewNag() //TODO: Create a proper test for this elsewhere.
|
||||
})
|
||||
|
||||
it('should remove reaction', async() => {
|
||||
await element(by.id('message-reaction-:grinning:')).tap();
|
||||
await waitFor(element(by.id('message-reaction-:grinning:'))).toBeNotVisible().withTimeout(60000);
|
||||
await expect(element(by.id('message-reaction-:grinning:'))).toBeNotVisible();
|
||||
});
|
||||
|
||||
it('should edit message', async() => {
|
||||
|
@ -241,7 +213,6 @@ describe('Room screen', () => {
|
|||
await element(by.id('messagebox-input')).typeText('ed');
|
||||
await element(by.id('messagebox-send-message')).tap();
|
||||
await waitFor(element(by.label(`${ data.random }edited (edited)`)).atIndex(0)).toExist().withTimeout(60000);
|
||||
await expect(element(by.label(`${ data.random }edited (edited)`)).atIndex(0)).toExist();
|
||||
});
|
||||
|
||||
it('should quote message', async() => {
|
||||
|
@ -253,34 +224,29 @@ describe('Room screen', () => {
|
|||
await element(by.label('Quote')).tap();
|
||||
await element(by.id('messagebox-input')).typeText(`${ data.random }quoted`);
|
||||
await element(by.id('messagebox-send-message')).tap();
|
||||
await sleep(1000);
|
||||
|
||||
// TODO: test if quote was sent
|
||||
});
|
||||
|
||||
it('should pin message', async() => {
|
||||
await waitFor(element(by.label(`${ data.random }edited (edited)`)).atIndex(0)).toExist();
|
||||
await element(by.label(`${ data.random }edited (edited)`)).atIndex(0).longPress();
|
||||
await expect(element(by.id('action-sheet'))).toExist();
|
||||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
await element(by.label('Pin')).tap();
|
||||
await waitFor(element(by.id('action-sheet'))).toNotExist().withTimeout(5000);
|
||||
await sleep(1500);
|
||||
await mockMessage('pin')
|
||||
await pinMessage('pin')
|
||||
|
||||
await waitFor(element(by.label(`${ data.random }edited (edited)`)).atIndex(0)).toBeVisible();
|
||||
await element(by.label(`${ data.random }edited (edited)`)).atIndex(0).longPress();
|
||||
await expect(element(by.id('action-sheet'))).toExist();
|
||||
await waitFor(element(by.label(`${ data.random }pin`)).atIndex(0)).toBeVisible().withTimeout(2000);
|
||||
await waitFor(element(by.label('Message pinned')).atIndex(0)).toBeVisible().withTimeout(2000);
|
||||
await element(by.label(`${ data.random }pin`)).atIndex(0).longPress();
|
||||
await waitFor(element(by.id('action-sheet'))).toExist().withTimeout(1000);
|
||||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
await waitFor(element(by.label('Unpin'))).toBeVisible().withTimeout(2000);
|
||||
await expect(element(by.label('Unpin'))).toBeVisible();
|
||||
await element(by.id('action-sheet-backdrop')).tap();
|
||||
});
|
||||
|
||||
it('should delete message', async() => {
|
||||
await waitFor(element(by.label(`${ data.random }quoted`)).atIndex(0)).toBeVisible();
|
||||
await element(by.label(`${ data.random }quoted`)).atIndex(0).longPress();
|
||||
await mockMessage('delete')
|
||||
|
||||
await waitFor(element(by.label(`${ data.random }delete`)).atIndex(0)).toBeVisible();
|
||||
await element(by.label(`${ data.random }delete`)).atIndex(0).longPress();
|
||||
await expect(element(by.id('action-sheet'))).toExist();
|
||||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||
|
@ -288,11 +254,9 @@ describe('Room screen', () => {
|
|||
|
||||
const deleteAlertMessage = 'You will not be able to recover this message!';
|
||||
await waitFor(element(by.text(deleteAlertMessage)).atIndex(0)).toExist().withTimeout(10000);
|
||||
await expect(element(by.text(deleteAlertMessage)).atIndex(0)).toExist();
|
||||
await element(by.text('Delete')).tap();
|
||||
|
||||
await sleep(1000);
|
||||
await expect(element(by.label(`${ data.random }quoted`)).atIndex(0)).toNotExist();
|
||||
await waitFor(element(by.label(`${ data.random }delete`)).atIndex(0)).toNotExist().withTimeout(2000);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -317,7 +281,6 @@ describe('Room screen', () => {
|
|||
await waitFor(element(by.id(`room-view-title-${ thread }`))).toExist().withTimeout(5000);
|
||||
await expect(element(by.id(`room-view-title-${ thread }`))).toExist();
|
||||
await tapBack();
|
||||
await sleep(1000);
|
||||
});
|
||||
|
||||
it('should toggle follow thread', async() => {
|
||||
|
@ -332,10 +295,13 @@ describe('Room screen', () => {
|
|||
await waitFor(element(by.id('room-view-header-unfollow'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('room-view-header-unfollow'))).toExist();
|
||||
await tapBack();
|
||||
await sleep(1000);
|
||||
});
|
||||
|
||||
it('should navigate to thread from thread name', async() => {
|
||||
await waitFor(element(by.id('room-view-header-actions').and(by.label(` ${ mainRoom }`)))).toBeVisible().withTimeout(2000);
|
||||
await waitFor(element(by.id('room-view-header-actions').and(by.label(` ${ data.random }thread`)))).toBeNotVisible().withTimeout(2000);
|
||||
await sleep(500) //TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :(
|
||||
|
||||
await mockMessage('dummymessagebetweenthethread');
|
||||
await element(by.label(thread)).atIndex(0).longPress();
|
||||
await expect(element(by.id('action-sheet'))).toExist();
|
||||
|
@ -352,10 +318,10 @@ describe('Room screen', () => {
|
|||
await waitFor(element(by.id(`room-view-title-${ thread }`))).toExist().withTimeout(5000);
|
||||
await expect(element(by.id(`room-view-title-${ thread }`))).toExist();
|
||||
await tapBack();
|
||||
await sleep(1000);
|
||||
});
|
||||
|
||||
it('should navigate to thread from threads view', async() => {
|
||||
await waitFor(element(by.id('room-view-header-threads'))).toExist().withTimeout(1000);
|
||||
await element(by.id('room-view-header-threads')).tap();
|
||||
await waitFor(element(by.id('thread-messages-view'))).toExist().withTimeout(5000);
|
||||
await expect(element(by.id('thread-messages-view'))).toExist();
|
||||
|
|
|
@ -2,7 +2,7 @@ const {
|
|||
device, expect, element, by, waitFor
|
||||
} = require('detox');
|
||||
const data = require('../../data');
|
||||
const { tapBack, sleep, searchRoom } = require('../../helpers/app');
|
||||
const { navigateToLogin, login, tapBack, sleep, searchRoom, mockMessage, starMessage, pinMessage } = require('../../helpers/app');
|
||||
|
||||
const scrollDown = 200;
|
||||
|
||||
|
@ -11,13 +11,12 @@ async function navigateToRoomActions(type) {
|
|||
if (type === 'd') {
|
||||
room = 'rocket.cat';
|
||||
} else {
|
||||
room = `private${ data.random }`;
|
||||
room = data.groups.private.name;
|
||||
}
|
||||
await searchRoom(room);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await element(by.id('room-view-header-actions')).tap();
|
||||
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
|
||||
}
|
||||
|
@ -25,7 +24,6 @@ async function navigateToRoomActions(type) {
|
|||
async function backToActions() {
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('room-actions-view'))).toExist();
|
||||
}
|
||||
|
||||
async function backToRoomsList() {
|
||||
|
@ -36,10 +34,16 @@ async function backToRoomsList() {
|
|||
}
|
||||
|
||||
describe('Room actions screen', () => {
|
||||
|
||||
before(async() => {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await navigateToLogin();
|
||||
await login(data.users.regular.username, data.users.regular.password);
|
||||
});
|
||||
|
||||
describe('Render', async() => {
|
||||
describe('Direct', async() => {
|
||||
before(async() => {
|
||||
await device.launchApp({ newInstance: true });
|
||||
await navigateToRoomActions('d');
|
||||
});
|
||||
|
||||
|
@ -197,65 +201,89 @@ describe('Room actions screen', () => {
|
|||
it('should show mentioned messages', async() => {
|
||||
await element(by.id('room-actions-mentioned')).tap();
|
||||
await waitFor(element(by.id('mentioned-messages-view'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('mentioned-messages-view'))).toExist();
|
||||
// await waitFor(element(by.text(` ${ data.random }mention`))).toExist().withTimeout(60000);
|
||||
// await expect(element(by.text(` ${ data.random }mention`))).toExist();
|
||||
await backToActions();
|
||||
});
|
||||
|
||||
it('should show starred message and unstar it', async() => {
|
||||
|
||||
//Go back to room and send a message
|
||||
await tapBack();
|
||||
await mockMessage('messageToStar');
|
||||
|
||||
//Star the message
|
||||
await starMessage('messageToStar')
|
||||
|
||||
//Back into Room Actions
|
||||
await element(by.id('room-view-header-actions')).tap();
|
||||
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
|
||||
|
||||
//Go to starred messages
|
||||
await element(by.id('room-actions-starred')).tap();
|
||||
await waitFor(element(by.id('starred-messages-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.label(`${ data.random }message`).withAncestor(by.id('starred-messages-view')))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.label(`${ data.random }message`).withAncestor(by.id('starred-messages-view')))).toBeVisible();
|
||||
await element(by.label(`${ data.random }message`).withAncestor(by.id('starred-messages-view'))).longPress();
|
||||
await waitFor(element(by.label(`${ data.random }messageToStar`).withAncestor(by.id('starred-messages-view')))).toBeVisible().withTimeout(60000);
|
||||
|
||||
//Unstar message
|
||||
await element(by.label(`${ data.random }messageToStar`).withAncestor(by.id('starred-messages-view'))).longPress();
|
||||
await expect(element(by.id('action-sheet'))).toExist();
|
||||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.label('Unstar')).tap();
|
||||
|
||||
await waitFor(element(by.label(`${ data.random }message`).withAncestor(by.id('starred-messages-view')))).toBeNotVisible().withTimeout(60000);
|
||||
await expect(element(by.label(`${ data.random }message`).withAncestor(by.id('starred-messages-view')))).toBeNotVisible();
|
||||
await waitFor(element(by.label(`${ data.random }messageToStar`).withAncestor(by.id('starred-messages-view')))).toBeNotVisible().withTimeout(60000);
|
||||
await backToActions();
|
||||
});
|
||||
|
||||
it('should show pinned message and unpin it', async() => {
|
||||
|
||||
//Go back to room and send a message
|
||||
await tapBack();
|
||||
await mockMessage('messageToPin');
|
||||
|
||||
//Pin the message
|
||||
await pinMessage('messageToPin')
|
||||
|
||||
//Back into Room Actions
|
||||
await element(by.id('room-view-header-actions')).tap();
|
||||
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
|
||||
|
||||
await waitFor(element(by.id('room-actions-pinned'))).toExist();
|
||||
await element(by.id('room-actions-pinned')).tap();
|
||||
await waitFor(element(by.id('pinned-messages-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.label(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view')))).toBeVisible().withTimeout(60000);
|
||||
await expect(element(by.label(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view')))).toBeVisible();
|
||||
await element(by.label(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view'))).longPress();
|
||||
await waitFor(element(by.label(`${ data.random }messageToPin`).withAncestor(by.id('pinned-messages-view')))).toBeVisible().withTimeout(60000);
|
||||
await element(by.label(`${ data.random }messageToPin`).withAncestor(by.id('pinned-messages-view'))).longPress();
|
||||
|
||||
await expect(element(by.id('action-sheet'))).toExist();
|
||||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||
await element(by.label('Unpin')).tap();
|
||||
|
||||
await waitFor(element(by.label(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view')))).toBeNotVisible().withTimeout(60000);
|
||||
await expect(element(by.label(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view')))).toBeNotVisible();
|
||||
await waitFor(element(by.label(`${ data.random }messageToPin`).withAncestor(by.id('pinned-messages-view')))).toBeNotVisible().withTimeout(60000);
|
||||
await backToActions();
|
||||
});
|
||||
|
||||
it('should search and find a message', async() => {
|
||||
|
||||
//Go back to room and send a message
|
||||
await tapBack();
|
||||
await mockMessage('messageToFind');
|
||||
|
||||
//Back into Room Actions
|
||||
await element(by.id('room-view-header-actions')).tap();
|
||||
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
|
||||
|
||||
await element(by.id('room-actions-search')).tap();
|
||||
await waitFor(element(by.id('search-messages-view'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('search-message-view-input'))).toExist();
|
||||
await element(by.id('search-message-view-input')).replaceText(`/${ data.random }message/`);
|
||||
await waitFor(element(by.label(`${ data.random }message`).withAncestor(by.id('search-messages-view')))).toExist().withTimeout(60000);
|
||||
await expect(element(by.label(`${ data.random }message`).withAncestor(by.id('search-messages-view')))).toExist();
|
||||
await element(by.id('search-message-view-input')).replaceText(`/${ data.random }messageToFind/`);
|
||||
await waitFor(element(by.label(`${ data.random }messageToFind`).withAncestor(by.id('search-messages-view')))).toExist().withTimeout(60000);
|
||||
await backToActions();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Notification', async() => {
|
||||
it('should navigate to notification preference view', async() => {
|
||||
await waitFor(element(by.id('room-actions-notifications'))).toExist();
|
||||
await expect(element(by.id('room-actions-notifications'))).toExist();
|
||||
await waitFor(element(by.id('room-actions-notifications'))).toExist().withTimeout(2000);
|
||||
await element(by.id('room-actions-notifications')).tap();
|
||||
await waitFor(element(by.id('notification-preference-view'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('notification-preference-view'))).toExist();
|
||||
});
|
||||
|
||||
it('should have receive notification option', async() => {
|
||||
|
@ -271,30 +299,25 @@ describe('Room actions screen', () => {
|
|||
});
|
||||
|
||||
it('should have push notification option', async() => {
|
||||
await waitFor(element(by.id('notification-preference-view-push-notification'))).toExist();
|
||||
await expect(element(by.id('notification-preference-view-push-notification'))).toExist();
|
||||
await waitFor(element(by.id('notification-preference-view-push-notification'))).toExist().withTimeout(4000);
|
||||
});
|
||||
|
||||
it('should have notification audio option', async() => {
|
||||
await waitFor(element(by.id('notification-preference-view-audio'))).toExist();
|
||||
await expect(element(by.id('notification-preference-view-audio'))).toExist();
|
||||
await waitFor(element(by.id('notification-preference-view-audio'))).toExist().withTimeout(4000);
|
||||
});
|
||||
|
||||
it('should have notification sound option', async() => {
|
||||
// Ugly hack to scroll on detox
|
||||
await element(by.type('UIScrollView')).atIndex(1).scrollTo('bottom');
|
||||
await waitFor(element(by.id('notification-preference-view-sound'))).toExist();
|
||||
await expect(element(by.id('notification-preference-view-sound'))).toExist();
|
||||
await waitFor(element(by.id('notification-preference-view-sound'))).toExist().withTimeout(4000);
|
||||
});
|
||||
|
||||
it('should have notification duration option', async() => {
|
||||
await waitFor(element(by.id('notification-preference-view-notification-duration'))).toExist();
|
||||
await expect(element(by.id('notification-preference-view-notification-duration'))).toExist();
|
||||
await waitFor(element(by.id('notification-preference-view-notification-duration'))).toExist().withTimeout(4000);
|
||||
});
|
||||
|
||||
it('should have email alert option', async() => {
|
||||
await waitFor(element(by.id('notification-preference-view-email-alert'))).toExist();
|
||||
await expect(element(by.id('notification-preference-view-email-alert'))).toExist();
|
||||
await waitFor(element(by.id('notification-preference-view-email-alert'))).toExist().withTimeout(4000);
|
||||
});
|
||||
|
||||
after(async() => {
|
||||
|
@ -309,34 +332,28 @@ describe('Room actions screen', () => {
|
|||
const user = data.users.alternate
|
||||
|
||||
it('should tap on leave channel and raise alert', async() => {
|
||||
await waitFor(element(by.id('room-actions-leave-channel'))).toExist();
|
||||
await expect(element(by.id('room-actions-leave-channel'))).toExist();
|
||||
await waitFor(element(by.id('room-actions-leave-channel'))).toExist().withTimeout(2000);
|
||||
await element(by.id('room-actions-leave-channel')).tap();
|
||||
await waitFor(element(by.text('Yes, leave it!'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.text('Yes, leave it!'))).toExist();
|
||||
await element(by.text('Yes, leave it!')).tap();
|
||||
await waitFor(element(by.text('You are the last owner. Please set new owner before leaving the room.'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.text('You are the last owner. Please set new owner before leaving the room.'))).toExist();
|
||||
await waitFor(element(by.text('You are the last owner. Please set new owner before leaving the room.'))).toExist().withTimeout(8000);
|
||||
await element(by.text('OK')).tap();
|
||||
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000);
|
||||
});
|
||||
|
||||
it('should add user to the room', async() => {
|
||||
await waitFor(element(by.id('room-actions-add-user'))).toExist();
|
||||
await waitFor(element(by.id('room-actions-add-user'))).toExist().withTimeout(4000);
|
||||
await element(by.id('room-actions-add-user')).tap();
|
||||
await element(by.id('select-users-view-search')).tap();
|
||||
await element(by.id('select-users-view-search')).replaceText(user.username);
|
||||
await waitFor(element(by.id(`select-users-view-item-${ user.username }`))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id(`select-users-view-item-${ user.username }`))).toExist();
|
||||
await waitFor(element(by.id(`select-users-view-item-${ user.username }`))).toExist().withTimeout(10000);
|
||||
await element(by.id(`select-users-view-item-${ user.username }`)).tap();
|
||||
await waitFor(element(by.id(`selected-user-${ user.username }`))).toExist().withTimeout(5000);
|
||||
await expect(element(by.id(`selected-user-${ user.username }`))).toExist();
|
||||
await element(by.id('selected-users-view-submit')).tap();
|
||||
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000);
|
||||
await element(by.id('room-actions-members')).tap();
|
||||
await element(by.id('room-members-view-toggle-status')).tap();
|
||||
await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id(`room-members-view-item-${ user.username }`))).toExist();
|
||||
await backToActions(1);
|
||||
});
|
||||
|
||||
|
@ -344,26 +361,20 @@ describe('Room actions screen', () => {
|
|||
before(async() => {
|
||||
await element(by.id('room-actions-members')).tap();
|
||||
await waitFor(element(by.id('room-members-view'))).toExist().withTimeout(2000);
|
||||
await expect(element(by.id('room-members-view'))).toExist();
|
||||
});
|
||||
|
||||
it('should show all users', async() => {
|
||||
await sleep(1000);
|
||||
await element(by.id('room-members-view-toggle-status')).tap();
|
||||
await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id(`room-members-view-item-${ user.username }`))).toExist();
|
||||
});
|
||||
|
||||
it('should filter user', async() => {
|
||||
await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id(`room-members-view-item-${ user.username }`))).toExist();
|
||||
await element(by.id('room-members-view-search')).replaceText('rocket');
|
||||
await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toBeNotVisible().withTimeout(60000);
|
||||
await expect(element(by.id(`room-members-view-item-${ user.username }`))).toBeNotVisible();
|
||||
await element(by.id('room-members-view-search')).tap();
|
||||
await element(by.id('room-members-view-search')).clearText('');
|
||||
await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id(`room-members-view-item-${ user.username }`))).toExist();
|
||||
});
|
||||
|
||||
// FIXME: mute/unmute isn't working
|
||||
|
@ -391,9 +402,7 @@ describe('Room actions screen', () => {
|
|||
await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(5000);
|
||||
await element(by.id(`room-members-view-item-${ user.username }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('room-view'))).toExist();
|
||||
await waitFor(element(by.id(`room-view-title-${ user.username }`))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id(`room-view-title-${ user.username }`))).toExist();
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000);
|
||||
});
|
||||
|
@ -407,13 +416,10 @@ describe('Room actions screen', () => {
|
|||
|
||||
it('should block/unblock user', async() => {
|
||||
await waitFor(element(by.id('room-actions-block-user'))).toExist();
|
||||
await sleep(1000);
|
||||
await element(by.id('room-actions-block-user')).tap();
|
||||
await waitFor(element(by.label('Unblock user'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.label('Unblock user'))).toExist();
|
||||
await element(by.id('room-actions-block-user')).tap();
|
||||
await waitFor(element(by.label('Block user'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.label('Block user'))).toExist();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,23 +2,23 @@ const {
|
|||
device, expect, element, by, waitFor
|
||||
} = require('detox');
|
||||
const data = require('../../data');
|
||||
const { tapBack, sleep, searchRoom } = require('../../helpers/app');
|
||||
const { navigateToLogin, login, tapBack, sleep, searchRoom } = require('../../helpers/app');
|
||||
|
||||
const privateRoomName = data.groups.private.name
|
||||
|
||||
async function navigateToRoomInfo(type) {
|
||||
let room;
|
||||
if (type === 'd') {
|
||||
room = 'rocket.cat';
|
||||
} else {
|
||||
room = `private${ data.random }`;
|
||||
room = privateRoomName;
|
||||
}
|
||||
await searchRoom(room);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await element(by.id('room-view-header-actions')).tap();
|
||||
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
|
||||
await sleep(1000);
|
||||
await element(by.id('room-actions-info')).tap();
|
||||
await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000);
|
||||
}
|
||||
|
@ -28,13 +28,19 @@ async function waitForToast() {
|
|||
// await expect(element(by.id('toast'))).toExist();
|
||||
// await waitFor(element(by.id('toast'))).toBeNotVisible().withTimeout(10000);
|
||||
// await expect(element(by.id('toast'))).toBeNotVisible();
|
||||
await sleep(5000);
|
||||
await sleep(1);
|
||||
}
|
||||
|
||||
describe('Room info screen', () => {
|
||||
|
||||
before(async() => {
|
||||
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
|
||||
await navigateToLogin();
|
||||
await login(data.users.regular.username, data.users.regular.password);
|
||||
});
|
||||
|
||||
describe('Direct', async() => {
|
||||
before(async() => {
|
||||
await device.launchApp({ newInstance: true });
|
||||
await navigateToRoomInfo('d');
|
||||
});
|
||||
|
||||
|
@ -42,11 +48,16 @@ describe('Room info screen', () => {
|
|||
await expect(element(by.id('room-info-view'))).toExist();
|
||||
await expect(element(by.id('room-info-view-name'))).toExist();
|
||||
});
|
||||
|
||||
after(async() => {
|
||||
await tapBack()
|
||||
await tapBack()
|
||||
await tapBack()
|
||||
})
|
||||
});
|
||||
|
||||
describe('Channel/Group', async() => {
|
||||
before(async() => {
|
||||
await device.launchApp({ newInstance: true });
|
||||
await navigateToRoomInfo('c');
|
||||
});
|
||||
|
||||
|
@ -78,7 +89,6 @@ describe('Room info screen', () => {
|
|||
|
||||
describe('Render Edit', async() => {
|
||||
before(async() => {
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000);
|
||||
await element(by.id('room-info-view-edit-button')).tap();
|
||||
await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
|
||||
|
@ -141,7 +151,6 @@ describe('Room info screen', () => {
|
|||
});
|
||||
|
||||
describe('Usage', async() => {
|
||||
const room = `private${ data.random }`;
|
||||
// it('should enter "invalid name" and get error', async() => {
|
||||
// await element(by.type('UIScrollView')).atIndex(1).swipe('down');
|
||||
// await element(by.id('room-info-edit-view-name')).replaceText('invalid name');
|
||||
|
@ -155,22 +164,17 @@ describe('Room info screen', () => {
|
|||
// });
|
||||
|
||||
it('should change room name', async() => {
|
||||
await element(by.id('room-info-edit-view-name')).replaceText(`${ room }new`);
|
||||
await element(by.id('room-info-edit-view-name')).replaceText(`${ privateRoomName }new`);
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await element(by.id('room-info-edit-view-submit')).tap();
|
||||
await sleep(5000);
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await expect(element(by.id('room-info-view-name'))).toHaveLabel(`${ room }new`);
|
||||
await expect(element(by.id('room-info-view-name'))).toHaveLabel(`${ privateRoomName }new`);
|
||||
// change name to original
|
||||
await element(by.id('room-info-view-edit-button')).tap();
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await element(by.id('room-info-edit-view-name')).replaceText(`${ room }`);
|
||||
await element(by.id('room-info-edit-view-name')).replaceText(`${ privateRoomName }`);
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await sleep(1000);
|
||||
await element(by.id('room-info-edit-view-submit')).tap();
|
||||
await waitForToast();
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('down');
|
||||
|
@ -184,14 +188,11 @@ describe('Room info screen', () => {
|
|||
await element(by.id('room-info-edit-view-password')).replaceText('abc');
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await element(by.id('room-info-edit-view-t')).tap();
|
||||
await sleep(1000);
|
||||
await element(by.id('room-info-edit-view-ro')).tap();
|
||||
await sleep(1000);
|
||||
await element(by.id('room-info-edit-view-ro')).longPress(); //https://github.com/facebook/react-native/issues/28032
|
||||
await element(by.id('room-info-edit-view-react-when-ro')).tap();
|
||||
await sleep(1000);
|
||||
await element(by.id('room-info-edit-view-reset')).tap();
|
||||
// after reset
|
||||
await expect(element(by.id('room-info-edit-view-name'))).toHaveText(room);
|
||||
await expect(element(by.id('room-info-edit-view-name'))).toHaveText(privateRoomName);
|
||||
await expect(element(by.id('room-info-edit-view-description'))).toHaveText('');
|
||||
await expect(element(by.id('room-info-edit-view-topic'))).toHaveText('');
|
||||
await expect(element(by.id('room-info-edit-view-announcement'))).toHaveText('');
|
||||
|
@ -203,55 +204,45 @@ describe('Room info screen', () => {
|
|||
});
|
||||
|
||||
it('should change room description', async() => {
|
||||
await sleep(1000);
|
||||
await element(by.id('room-info-edit-view-description')).replaceText('new description');
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await element(by.id('room-info-edit-view-submit')).tap();
|
||||
await waitForToast();
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await expect(element(by.label('new description').withAncestor(by.id('room-info-view-description')))).toExist();
|
||||
});
|
||||
|
||||
it('should change room topic', async() => {
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000);
|
||||
await element(by.id('room-info-view-edit-button')).tap();
|
||||
await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await element(by.id('room-info-edit-view-topic')).replaceText('new topic');
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await element(by.id('room-info-edit-view-submit')).tap();
|
||||
await waitForToast();
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await expect(element(by.label('new topic').withAncestor(by.id('room-info-view-topic')))).toExist();
|
||||
});
|
||||
|
||||
it('should change room announcement', async() => {
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000);
|
||||
await element(by.id('room-info-view-edit-button')).tap();
|
||||
await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement');
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await element(by.id('room-info-edit-view-submit')).tap();
|
||||
await waitForToast();
|
||||
await tapBack();
|
||||
await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await expect(element(by.label('new announcement').withAncestor(by.id('room-info-view-announcement')))).toExist();
|
||||
});
|
||||
|
||||
it('should change room password', async() => {
|
||||
await sleep(1000);
|
||||
await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000);
|
||||
await element(by.id('room-info-view-edit-button')).tap();
|
||||
await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
|
||||
await sleep(1000);
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await element(by.id('room-info-edit-view-password')).replaceText('password');
|
||||
await element(by.id('room-info-edit-view-submit')).tap();
|
||||
|
@ -259,7 +250,6 @@ describe('Room info screen', () => {
|
|||
});
|
||||
|
||||
it('should change room type', async() => {
|
||||
await sleep(1000);
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await element(by.id('room-info-edit-view-t')).tap();
|
||||
await element(by.id('room-info-edit-view-submit')).tap();
|
||||
|
@ -282,14 +272,11 @@ describe('Room info screen', () => {
|
|||
// });
|
||||
|
||||
it('should archive room', async() => {
|
||||
await sleep(1000);
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await element(by.id('room-info-edit-view-archive')).tap();
|
||||
await waitFor(element(by.text('Yes, archive it!'))).toExist().withTimeout(5000);
|
||||
await expect(element(by.text('Yes, archive it!'))).toExist();
|
||||
await element(by.text('Yes, archive it!')).tap();
|
||||
await waitFor(element(by.id('room-info-edit-view-unarchive'))).toExist().withTimeout(60000);
|
||||
await expect(element(by.id('room-info-edit-view-unarchive'))).toExist();
|
||||
await expect(element(by.id('room-info-edit-view-archive'))).toBeNotVisible();
|
||||
// TODO: needs permission to unarchive
|
||||
// await element(by.id('room-info-edit-view-archive')).tap();
|
||||
|
@ -301,16 +288,12 @@ describe('Room info screen', () => {
|
|||
});
|
||||
|
||||
it('should delete room', async() => {
|
||||
await sleep(1000);
|
||||
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
|
||||
await element(by.id('room-info-edit-view-delete')).tap();
|
||||
await waitFor(element(by.text('Yes, delete it!'))).toExist().withTimeout(5000);
|
||||
await expect(element(by.text('Yes, delete it!'))).toExist();
|
||||
await element(by.text('Yes, delete it!')).tap();
|
||||
await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000);
|
||||
await sleep(2000);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible().withTimeout(60000);
|
||||
await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible();
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ privateRoomName }`))).toBeNotVisible().withTimeout(60000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
136
ios/Podfile
136
ios/Podfile
|
@ -1,143 +1,23 @@
|
|||
platform :ios, '11.0'
|
||||
require_relative '../node_modules/react-native/scripts/react_native_pods'
|
||||
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
|
||||
require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
|
||||
|
||||
def add_flipper_pods!(versions = {})
|
||||
versions['Flipper'] ||= '~> 0.33.1'
|
||||
versions['DoubleConversion'] ||= '1.1.7'
|
||||
versions['Flipper-Folly'] ||= '~> 2.1'
|
||||
versions['Flipper-Glog'] ||= '0.3.6'
|
||||
versions['Flipper-PeerTalk'] ||= '~> 0.0.4'
|
||||
versions['Flipper-RSocket'] ||= '~> 1.0'
|
||||
|
||||
pod 'FlipperKit', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitLayoutPlugin', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/SKIOSNetworkPlugin', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitUserDefaultsPlugin', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitReactPlugin', versions['Flipper'], :configuration => 'Debug'
|
||||
|
||||
# List all transitive dependencies for FlipperKit pods
|
||||
# to avoid them being linked in Release builds
|
||||
pod 'Flipper', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'Flipper-DoubleConversion', versions['DoubleConversion'], :configuration => 'Debug'
|
||||
pod 'Flipper-Folly', versions['Flipper-Folly'], :configuration => 'Debug'
|
||||
pod 'Flipper-Glog', versions['Flipper-Glog'], :configuration => 'Debug'
|
||||
pod 'Flipper-PeerTalk', versions['Flipper-PeerTalk'], :configuration => 'Debug'
|
||||
pod 'Flipper-RSocket', versions['Flipper-RSocket'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/Core', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/CppBridge', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FBCxxFollyDynamicConvert', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FBDefines', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FKPortForwarding', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitHighlightOverlay', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitLayoutTextSearchable', versions['Flipper'], :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitNetworkPlugin', versions['Flipper'], :configuration => 'Debug'
|
||||
end
|
||||
|
||||
# Post Install processing for Flipper
|
||||
def flipper_post_install(installer)
|
||||
installer.pods_project.targets.each do |target|
|
||||
if target.name == 'YogaKit'
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['SWIFT_VERSION'] = '4.1'
|
||||
end
|
||||
end
|
||||
end
|
||||
file_name = Dir.glob("*.xcodeproj")[0]
|
||||
app_project = Xcodeproj::Project.open(file_name)
|
||||
app_project.native_targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
cflags = config.build_settings['OTHER_CFLAGS'] || '$(inherited) '
|
||||
unless cflags.include? '-DFB_SONARKIT_ENABLED=1'
|
||||
puts 'Adding -DFB_SONARKIT_ENABLED=1 in OTHER_CFLAGS...'
|
||||
cflags << '-DFB_SONARKIT_ENABLED=1'
|
||||
end
|
||||
config.build_settings['OTHER_CFLAGS'] = cflags
|
||||
end
|
||||
app_project.save
|
||||
end
|
||||
installer.pods_project.save
|
||||
def all_pods
|
||||
config = use_native_modules!
|
||||
use_unimodules!
|
||||
use_react_native!(:path => config["reactNativePath"])
|
||||
use_flipper!
|
||||
end
|
||||
|
||||
target 'RocketChatRN' do
|
||||
pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
|
||||
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
|
||||
pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
|
||||
pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
|
||||
pod 'React', :path => '../node_modules/react-native/'
|
||||
pod 'React-Core', :path => '../node_modules/react-native/'
|
||||
pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
|
||||
pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
|
||||
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
|
||||
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
|
||||
pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
|
||||
pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
|
||||
pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
|
||||
pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
|
||||
pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
|
||||
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
|
||||
pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
|
||||
pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
|
||||
|
||||
pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
|
||||
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
|
||||
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
|
||||
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
|
||||
pod 'ReactCommon/callinvoker', :path => "../node_modules/react-native/ReactCommon"
|
||||
pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
|
||||
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga', :modular_headers => true
|
||||
|
||||
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
|
||||
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
|
||||
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
|
||||
|
||||
use_native_modules!
|
||||
use_unimodules!
|
||||
all_pods
|
||||
end
|
||||
|
||||
target 'ShareRocketChatRN' do
|
||||
pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
|
||||
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
|
||||
pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
|
||||
pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
|
||||
pod 'React', :path => '../node_modules/react-native/'
|
||||
pod 'React-Core', :path => '../node_modules/react-native/'
|
||||
pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
|
||||
pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
|
||||
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
|
||||
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
|
||||
pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
|
||||
pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
|
||||
pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
|
||||
pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
|
||||
pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
|
||||
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
|
||||
pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
|
||||
pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
|
||||
|
||||
pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
|
||||
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
|
||||
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
|
||||
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
|
||||
pod 'ReactCommon/callinvoker', :path => "../node_modules/react-native/ReactCommon"
|
||||
pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
|
||||
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga', :modular_headers => true
|
||||
|
||||
pod 'JitsiMeetSDK', :git => 'https://github.com/RocketChat/jitsi-meet-ios-sdk-releases.git'
|
||||
|
||||
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
|
||||
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
|
||||
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
|
||||
|
||||
use_native_modules!
|
||||
use_unimodules!
|
||||
all_pods
|
||||
end
|
||||
|
||||
# Enables Flipper.
|
||||
#
|
||||
# Note that if you have use_frameworks! enabled, Flipper will not work and
|
||||
# you should disable these next few lines.
|
||||
add_flipper_pods!
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
|
|
799
ios/Podfile.lock
799
ios/Podfile.lock
File diff suppressed because it is too large
Load Diff
|
@ -1 +0,0 @@
|
|||
We've now combined all our supported platforms into a single podspec. As a result, we moved our submit script to a new location for Cocoapods projects: ${PODS_ROOT}/Crashlytics/submit. To avoid breaking functionality that references the old location of the submit, we've placed this dummy script that calls to the correct location, while providing a helpful warning if it is invoked. This bridge for backwards compatibility will be removed in a future release, so please heed the warning!
|
|
@ -1,6 +0,0 @@
|
|||
if [[ -z $PODS_ROOT ]]; then
|
||||
echo "error: The submit binary delivered by cocoapods is in a new location, under '$"{"PODS_ROOT"}"/Crashlytics/submit'. This script was put in place for backwards compatibility, but it relies on PODS_ROOT, which does not have a value in your current setup. Please update the path to the submit binary to fix this issue."
|
||||
else
|
||||
echo "warning: The submit script is now located at '$"{"PODS_ROOT"}"/Crashlytics/submit'. To remove this warning, update your path to point to this new location."
|
||||
sh "${PODS_ROOT}/Crashlytics/submit" "$@"
|
||||
fi
|
|
@ -1,23 +0,0 @@
|
|||
|
||||
# Crashlytics
|
||||
|
||||
## Overview
|
||||
|
||||
[Crashlytics](https://firebase.google.com/docs/crashlytics/get-started?platform=ios) offers the most powerful, yet lightest weight crash reporting solution for iOS.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
To start using Crashlytics, there are two options:
|
||||
|
||||
1) The recommended way is to go to the [Firebase Crashlytics Docs](https://firebase.google.com/docs/crashlytics/get-started?platform=ios) and follow the directions there.
|
||||
|
||||
2) If you aren't using Firebase yet, go to [Fabric Kits](https://fabric.io/kits), and follow the directions for Crashlytics.
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
* [API Reference](https://firebase.google.com/docs/reference/ios/crashlytics/api/reference/Classes)
|
||||
* [Forums](https://stackoverflow.com/questions/tagged/google-fabric)
|
||||
* [Website](https://firebase.google.com/docs/crashlytics)
|
||||
* Follow us on Twitter: [@crashlytics](https://twitter.com/crashlytics)
|
Binary file not shown.
|
@ -1,31 +0,0 @@
|
|||
//
|
||||
// ANSCompatibility.h
|
||||
// AnswersKit
|
||||
//
|
||||
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !__has_feature(nullability)
|
||||
#define nonnull
|
||||
#define nullable
|
||||
#define _Nullable
|
||||
#define _Nonnull
|
||||
#endif
|
||||
|
||||
#ifndef NS_ASSUME_NONNULL_BEGIN
|
||||
#define NS_ASSUME_NONNULL_BEGIN
|
||||
#endif
|
||||
|
||||
#ifndef NS_ASSUME_NONNULL_END
|
||||
#define NS_ASSUME_NONNULL_END
|
||||
#endif
|
||||
|
||||
#if __has_feature(objc_generics)
|
||||
#define ANS_GENERIC_NSARRAY(type) NSArray<type>
|
||||
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
|
||||
#else
|
||||
#define ANS_GENERIC_NSARRAY(type) NSArray
|
||||
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
|
||||
#endif
|
|
@ -1,210 +0,0 @@
|
|||
//
|
||||
// Answers.h
|
||||
// Crashlytics
|
||||
//
|
||||
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ANSCompatibility.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* This class exposes the Answers Events API, allowing you to track key
|
||||
* user user actions and metrics in your app.
|
||||
*/
|
||||
@interface Answers : NSObject
|
||||
|
||||
/**
|
||||
* Log a Sign Up event to see users signing up for your app in real-time, understand how
|
||||
* many users are signing up with different methods and their success rate signing up.
|
||||
*
|
||||
* @param signUpMethodOrNil The method by which a user logged in, e.g. Twitter or Digits.
|
||||
* @param signUpSucceededOrNil The ultimate success or failure of the login
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logSignUpWithMethod:(nullable NSString *)signUpMethodOrNil
|
||||
success:(nullable NSNumber *)signUpSucceededOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log an Log In event to see users logging into your app in real-time, understand how many
|
||||
* users are logging in with different methods and their success rate logging into your app.
|
||||
*
|
||||
* @param loginMethodOrNil The method by which a user logged in, e.g. email, Twitter or Digits.
|
||||
* @param loginSucceededOrNil The ultimate success or failure of the login
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logLoginWithMethod:(nullable NSString *)loginMethodOrNil
|
||||
success:(nullable NSNumber *)loginSucceededOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Share event to see users sharing from your app in real-time, letting you
|
||||
* understand what content they're sharing from the type or genre down to the specific id.
|
||||
*
|
||||
* @param shareMethodOrNil The method by which a user shared, e.g. email, Twitter, SMS.
|
||||
* @param contentNameOrNil The human readable name for this piece of content.
|
||||
* @param contentTypeOrNil The type of content shared.
|
||||
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logShareWithMethod:(nullable NSString *)shareMethodOrNil
|
||||
contentName:(nullable NSString *)contentNameOrNil
|
||||
contentType:(nullable NSString *)contentTypeOrNil
|
||||
contentId:(nullable NSString *)contentIdOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log an Invite Event to track how users are inviting other users into
|
||||
* your application.
|
||||
*
|
||||
* @param inviteMethodOrNil The method of invitation, e.g. GameCenter, Twitter, email.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logInviteWithMethod:(nullable NSString *)inviteMethodOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Purchase event to see your revenue in real-time, understand how many users are making purchases, see which
|
||||
* items are most popular, and track plenty of other important purchase-related metrics.
|
||||
*
|
||||
* @param itemPriceOrNil The purchased item's price.
|
||||
* @param currencyOrNil The ISO4217 currency code. Example: USD
|
||||
* @param purchaseSucceededOrNil Was the purchase successful or unsuccessful
|
||||
* @param itemNameOrNil The human-readable form of the item's name. Example:
|
||||
* @param itemTypeOrNil The type, or genre of the item. Example: Song
|
||||
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this purchase.
|
||||
*/
|
||||
+ (void)logPurchaseWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
|
||||
currency:(nullable NSString *)currencyOrNil
|
||||
success:(nullable NSNumber *)purchaseSucceededOrNil
|
||||
itemName:(nullable NSString *)itemNameOrNil
|
||||
itemType:(nullable NSString *)itemTypeOrNil
|
||||
itemId:(nullable NSString *)itemIdOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Level Start Event to track where users are in your game.
|
||||
*
|
||||
* @param levelNameOrNil The level name
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this level start event.
|
||||
*/
|
||||
+ (void)logLevelStart:(nullable NSString *)levelNameOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Level End event to track how users are completing levels in your game.
|
||||
*
|
||||
* @param levelNameOrNil The name of the level completed, E.G. "1" or "Training"
|
||||
* @param scoreOrNil The score the user completed the level with.
|
||||
* @param levelCompletedSuccesfullyOrNil A boolean representing whether or not the level was completed successfully.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logLevelEnd:(nullable NSString *)levelNameOrNil
|
||||
score:(nullable NSNumber *)scoreOrNil
|
||||
success:(nullable NSNumber *)levelCompletedSuccesfullyOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log an Add to Cart event to see users adding items to a shopping cart in real-time, understand how
|
||||
* many users start the purchase flow, see which items are most popular, and track plenty of other important
|
||||
* purchase-related metrics.
|
||||
*
|
||||
* @param itemPriceOrNil The purchased item's price.
|
||||
* @param currencyOrNil The ISO4217 currency code. Example: USD
|
||||
* @param itemNameOrNil The human-readable form of the item's name. Example:
|
||||
* @param itemTypeOrNil The type, or genre of the item. Example: Song
|
||||
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logAddToCartWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
|
||||
currency:(nullable NSString *)currencyOrNil
|
||||
itemName:(nullable NSString *)itemNameOrNil
|
||||
itemType:(nullable NSString *)itemTypeOrNil
|
||||
itemId:(nullable NSString *)itemIdOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Start Checkout event to see users moving through the purchase funnel in real-time, understand how many
|
||||
* users are doing this and how much they're spending per checkout, and see how it related to other important
|
||||
* purchase-related metrics.
|
||||
*
|
||||
* @param totalPriceOrNil The total price of the cart.
|
||||
* @param currencyOrNil The ISO4217 currency code. Example: USD
|
||||
* @param itemCountOrNil The number of items in the cart.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logStartCheckoutWithPrice:(nullable NSDecimalNumber *)totalPriceOrNil
|
||||
currency:(nullable NSString *)currencyOrNil
|
||||
itemCount:(nullable NSNumber *)itemCountOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Rating event to see users rating content within your app in real-time and understand what
|
||||
* content is most engaging, from the type or genre down to the specific id.
|
||||
*
|
||||
* @param ratingOrNil The integer rating given by the user.
|
||||
* @param contentNameOrNil The human readable name for this piece of content.
|
||||
* @param contentTypeOrNil The type of content shared.
|
||||
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logRating:(nullable NSNumber *)ratingOrNil
|
||||
contentName:(nullable NSString *)contentNameOrNil
|
||||
contentType:(nullable NSString *)contentTypeOrNil
|
||||
contentId:(nullable NSString *)contentIdOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Content View event to see users viewing content within your app in real-time and
|
||||
* understand what content is most engaging, from the type or genre down to the specific id.
|
||||
*
|
||||
* @param contentNameOrNil The human readable name for this piece of content.
|
||||
* @param contentTypeOrNil The type of content shared.
|
||||
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logContentViewWithName:(nullable NSString *)contentNameOrNil
|
||||
contentType:(nullable NSString *)contentTypeOrNil
|
||||
contentId:(nullable NSString *)contentIdOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Search event allows you to see users searching within your app in real-time and understand
|
||||
* exactly what they're searching for.
|
||||
*
|
||||
* @param queryOrNil The user's query.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
|
||||
*/
|
||||
+ (void)logSearchWithQuery:(nullable NSString *)queryOrNil
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
/**
|
||||
* Log a Custom Event to see user actions that are uniquely important for your app in real-time, to see how often
|
||||
* they're performing these actions with breakdowns by different categories you add. Use a human-readable name for
|
||||
* the name of the event, since this is how the event will appear in Answers.
|
||||
*
|
||||
* @param eventName The human-readable name for the event.
|
||||
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event. Attribute keys
|
||||
* must be <code>NSString</code> and values must be <code>NSNumber</code> or <code>NSString</code>.
|
||||
* @discussion How we treat <code>NSNumbers</code>:
|
||||
* We will provide information about the distribution of values over time.
|
||||
*
|
||||
* How we treat <code>NSStrings</code>:
|
||||
* NSStrings are used as categorical data, allowing comparison across different category values.
|
||||
* Strings are limited to a maximum length of 100 characters, attributes over this length will be
|
||||
* truncated.
|
||||
*
|
||||
* When tracking the Tweet views to better understand user engagement, sending the tweet's length
|
||||
* and the type of media present in the tweet allows you to track how tweet length and the type of media influence
|
||||
* engagement.
|
||||
*/
|
||||
+ (void)logCustomEventWithName:(NSString *)eventName
|
||||
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,33 +0,0 @@
|
|||
//
|
||||
// CLSAttributes.h
|
||||
// Crashlytics
|
||||
//
|
||||
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#define CLS_DEPRECATED(x) __attribute__ ((deprecated(x)))
|
||||
|
||||
#if !__has_feature(nullability)
|
||||
#define nonnull
|
||||
#define nullable
|
||||
#define _Nullable
|
||||
#define _Nonnull
|
||||
#endif
|
||||
|
||||
#ifndef NS_ASSUME_NONNULL_BEGIN
|
||||
#define NS_ASSUME_NONNULL_BEGIN
|
||||
#endif
|
||||
|
||||
#ifndef NS_ASSUME_NONNULL_END
|
||||
#define NS_ASSUME_NONNULL_END
|
||||
#endif
|
||||
|
||||
#if __has_feature(objc_generics)
|
||||
#define CLS_GENERIC_NSARRAY(type) NSArray<type>
|
||||
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
|
||||
#else
|
||||
#define CLS_GENERIC_NSARRAY(type) NSArray
|
||||
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
|
||||
#endif
|
|
@ -1,64 +0,0 @@
|
|||
//
|
||||
// CLSLogging.h
|
||||
// Crashlytics
|
||||
//
|
||||
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
#ifdef __OBJC__
|
||||
#import "CLSAttributes.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* The CLS_LOG macro provides as easy way to gather more information in your log messages that are
|
||||
* sent with your crash data. CLS_LOG prepends your custom log message with the function name and
|
||||
* line number where the macro was used. If your app was built with the DEBUG preprocessor macro
|
||||
* defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog.
|
||||
* If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only.
|
||||
*
|
||||
* Example output:
|
||||
* -[AppDelegate login:] line 134 $ login start
|
||||
*
|
||||
* If you would like to change this macro, create a new header file, unset our define and then define
|
||||
* your own version. Make sure this new header file is imported after the Crashlytics header file.
|
||||
*
|
||||
* #undef CLS_LOG
|
||||
* #define CLS_LOG(__FORMAT__, ...) CLSNSLog...
|
||||
*
|
||||
**/
|
||||
#ifdef __OBJC__
|
||||
#ifdef DEBUG
|
||||
#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
#else
|
||||
#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
* Add logging that will be sent with your crash data. This logging will not show up in the system.log
|
||||
* and will only be visible in your Crashlytics dashboard.
|
||||
*
|
||||
**/
|
||||
|
||||
#ifdef __OBJC__
|
||||
OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
|
||||
OBJC_EXTERN void CLSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
|
||||
|
||||
/**
|
||||
*
|
||||
* Add logging that will be sent with your crash data. This logging will show up in the system.log
|
||||
* and your Crashlytics dashboard. It is not recommended for Release builds.
|
||||
*
|
||||
**/
|
||||
OBJC_EXTERN void CLSNSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
|
||||
OBJC_EXTERN void CLSNSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
#endif
|
|
@ -1,103 +0,0 @@
|
|||
//
|
||||
// CLSReport.h
|
||||
// Crashlytics
|
||||
//
|
||||
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CLSAttributes.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* The CLSCrashReport protocol is deprecated. See the CLSReport class and the CrashyticsDelegate changes for details.
|
||||
**/
|
||||
@protocol CLSCrashReport <NSObject>
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *identifier;
|
||||
@property (nonatomic, copy, readonly) NSDictionary *customKeys;
|
||||
@property (nonatomic, copy, readonly) NSString *bundleVersion;
|
||||
@property (nonatomic, copy, readonly) NSString *bundleShortVersionString;
|
||||
@property (nonatomic, readonly, nullable) NSDate *crashedOnDate;
|
||||
@property (nonatomic, copy, readonly) NSString *OSVersion;
|
||||
@property (nonatomic, copy, readonly) NSString *OSBuildVersion;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* The CLSReport exposes an interface to the phsyical report that Crashlytics has created. You can
|
||||
* use this class to get information about the event, and can also set some values after the
|
||||
* event has occurred.
|
||||
**/
|
||||
@interface CLSReport : NSObject <CLSCrashReport>
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* Returns the session identifier for the report.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSString *identifier;
|
||||
|
||||
/**
|
||||
* Returns the custom key value data for the report.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSDictionary *customKeys;
|
||||
|
||||
/**
|
||||
* Returns the CFBundleVersion of the application that generated the report.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSString *bundleVersion;
|
||||
|
||||
/**
|
||||
* Returns the CFBundleShortVersionString of the application that generated the report.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSString *bundleShortVersionString;
|
||||
|
||||
/**
|
||||
* Returns the date that the report was created.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSDate *dateCreated;
|
||||
|
||||
/**
|
||||
* Returns the os version that the application crashed on.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSString *OSVersion;
|
||||
|
||||
/**
|
||||
* Returns the os build version that the application crashed on.
|
||||
**/
|
||||
@property (nonatomic, copy, readonly) NSString *OSBuildVersion;
|
||||
|
||||
/**
|
||||
* Returns YES if the report contains any crash information, otherwise returns NO.
|
||||
**/
|
||||
@property (nonatomic, assign, readonly) BOOL isCrash;
|
||||
|
||||
/**
|
||||
* You can use this method to set, after the event, additional custom keys. The rules
|
||||
* and semantics for this method are the same as those documented in Crashlytics.h. Be aware
|
||||
* that the maximum size and count of custom keys is still enforced, and you can overwrite keys
|
||||
* and/or cause excess keys to be deleted by using this method.
|
||||
**/
|
||||
- (void)setObjectValue:(nullable id)value forKey:(NSString *)key;
|
||||
|
||||
/**
|
||||
* Record an application-specific user identifier. See Crashlytics.h for details.
|
||||
**/
|
||||
@property (nonatomic, copy, nullable) NSString * userIdentifier;
|
||||
|
||||
/**
|
||||
* Record a user name. See Crashlytics.h for details.
|
||||
**/
|
||||
@property (nonatomic, copy, nullable) NSString * userName;
|
||||
|
||||
/**
|
||||
* Record a user email. See Crashlytics.h for details.
|
||||
**/
|
||||
@property (nonatomic, copy, nullable) NSString * userEmail;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,38 +0,0 @@
|
|||
//
|
||||
// CLSStackFrame.h
|
||||
// Crashlytics
|
||||
//
|
||||
// Copyright 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CLSAttributes.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
*
|
||||
* This class is used in conjunction with -[Crashlytics recordCustomExceptionName:reason:frameArray:] to
|
||||
* record information about non-ObjC/C++ exceptions. All information included here will be displayed
|
||||
* in the Crashlytics UI, and can influence crash grouping. Be particularly careful with the use of the
|
||||
* address property. If set, Crashlytics will attempt symbolication and could overwrite other properities
|
||||
* in the process.
|
||||
*
|
||||
**/
|
||||
@interface CLSStackFrame : NSObject
|
||||
|
||||
+ (instancetype)stackFrame;
|
||||
+ (instancetype)stackFrameWithAddress:(NSUInteger)address;
|
||||
+ (instancetype)stackFrameWithSymbol:(NSString *)symbol;
|
||||
|
||||
@property (nonatomic, copy, nullable) NSString *symbol;
|
||||
@property (nonatomic, copy, nullable) NSString *rawSymbol;
|
||||
@property (nonatomic, copy, nullable) NSString *library;
|
||||
@property (nonatomic, copy, nullable) NSString *fileName;
|
||||
@property (nonatomic, assign) uint32_t lineNumber;
|
||||
@property (nonatomic, assign) uint64_t offset;
|
||||
@property (nonatomic, assign) uint64_t address;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,288 +0,0 @@
|
|||
//
|
||||
// Crashlytics.h
|
||||
// Crashlytics
|
||||
//
|
||||
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "CLSAttributes.h"
|
||||
#import "CLSLogging.h"
|
||||
#import "CLSReport.h"
|
||||
#import "CLSStackFrame.h"
|
||||
#import "Answers.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol CrashlyticsDelegate;
|
||||
|
||||
/**
|
||||
* Crashlytics. Handles configuration and initialization of Crashlytics.
|
||||
*
|
||||
* Note: The Crashlytics class cannot be subclassed. If this is causing you pain for
|
||||
* testing, we suggest using either a wrapper class or a protocol extension.
|
||||
*/
|
||||
@interface Crashlytics : NSObject
|
||||
|
||||
@property (nonatomic, readonly, copy) NSString *APIKey;
|
||||
@property (nonatomic, readonly, copy) NSString *version;
|
||||
@property (nonatomic, assign) BOOL debugMode;
|
||||
|
||||
/**
|
||||
*
|
||||
* The delegate can be used to influence decisions on reporting and behavior, as well as reacting
|
||||
* to previous crashes.
|
||||
*
|
||||
* Make certain that the delegate is setup before starting Crashlytics with startWithAPIKey:... or
|
||||
* via +[Fabric with:...]. Failure to do will result in missing any delegate callbacks that occur
|
||||
* synchronously during start.
|
||||
*
|
||||
**/
|
||||
@property (nonatomic, assign, nullable) id <CrashlyticsDelegate> delegate;
|
||||
|
||||
/**
|
||||
* The recommended way to install Crashlytics into your application is to place a call to +startWithAPIKey:
|
||||
* in your -application:didFinishLaunchingWithOptions: or -applicationDidFinishLaunching:
|
||||
* method.
|
||||
*
|
||||
* Note: Starting with 3.0, the submission process has been significantly improved. The delay parameter
|
||||
* is no longer required to throttle submissions on launch, performance will be great without it.
|
||||
*
|
||||
* @param apiKey The Crashlytics API Key for this app
|
||||
*
|
||||
* @return The singleton Crashlytics instance
|
||||
*/
|
||||
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey;
|
||||
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey afterDelay:(NSTimeInterval)delay CLS_DEPRECATED("Crashlytics no longer needs or uses the delay parameter. Please use +startWithAPIKey: instead.");
|
||||
|
||||
/**
|
||||
* If you need the functionality provided by the CrashlyticsDelegate protocol, you can use
|
||||
* these convenience methods to activate the framework and set the delegate in one call.
|
||||
*
|
||||
* @param apiKey The Crashlytics API Key for this app
|
||||
* @param delegate A delegate object which conforms to CrashlyticsDelegate.
|
||||
*
|
||||
* @return The singleton Crashlytics instance
|
||||
*/
|
||||
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(nullable id<CrashlyticsDelegate>)delegate;
|
||||
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(nullable id<CrashlyticsDelegate>)delegate afterDelay:(NSTimeInterval)delay CLS_DEPRECATED("Crashlytics no longer needs or uses the delay parameter. Please use +startWithAPIKey:delegate: instead.");
|
||||
|
||||
/**
|
||||
* Access the singleton Crashlytics instance.
|
||||
*
|
||||
* @return The singleton Crashlytics instance
|
||||
*/
|
||||
+ (Crashlytics *)sharedInstance;
|
||||
|
||||
/**
|
||||
* The easiest way to cause a crash - great for testing!
|
||||
*/
|
||||
- (void)crash;
|
||||
|
||||
/**
|
||||
* The easiest way to cause a crash with an exception - great for testing.
|
||||
*/
|
||||
- (void)throwException;
|
||||
|
||||
/**
|
||||
* Specify a user identifier which will be visible in the Crashlytics UI.
|
||||
*
|
||||
* Many of our customers have requested the ability to tie crashes to specific end-users of their
|
||||
* application in order to facilitate responses to support requests or permit the ability to reach
|
||||
* out for more information. We allow you to specify up to three separate values for display within
|
||||
* the Crashlytics UI - but please be mindful of your end-user's privacy.
|
||||
*
|
||||
* We recommend specifying a user identifier - an arbitrary string that ties an end-user to a record
|
||||
* in your system. This could be a database id, hash, or other value that is meaningless to a
|
||||
* third-party observer but can be indexed and queried by you.
|
||||
*
|
||||
* Optionally, you may also specify the end-user's name or username, as well as email address if you
|
||||
* do not have a system that works well with obscured identifiers.
|
||||
*
|
||||
* Pursuant to our EULA, this data is transferred securely throughout our system and we will not
|
||||
* disseminate end-user data unless required to by law. That said, if you choose to provide end-user
|
||||
* contact information, we strongly recommend that you disclose this in your application's privacy
|
||||
* policy. Data privacy is of our utmost concern.
|
||||
*
|
||||
* @param identifier An arbitrary user identifier string which ties an end-user to a record in your system.
|
||||
*/
|
||||
- (void)setUserIdentifier:(nullable NSString *)identifier;
|
||||
|
||||
/**
|
||||
* Specify a user name which will be visible in the Crashlytics UI.
|
||||
* Please be mindful of your end-user's privacy and see if setUserIdentifier: can fulfil your needs.
|
||||
* @see setUserIdentifier:
|
||||
*
|
||||
* @param name An end user's name.
|
||||
*/
|
||||
- (void)setUserName:(nullable NSString *)name;
|
||||
|
||||
/**
|
||||
* Specify a user email which will be visible in the Crashlytics UI.
|
||||
* Please be mindful of your end-user's privacy and see if setUserIdentifier: can fulfil your needs.
|
||||
*
|
||||
* @see setUserIdentifier:
|
||||
*
|
||||
* @param email An end user's email address.
|
||||
*/
|
||||
- (void)setUserEmail:(nullable NSString *)email;
|
||||
|
||||
+ (void)setUserIdentifier:(nullable NSString *)identifier CLS_DEPRECATED("Please access this method via +sharedInstance");
|
||||
+ (void)setUserName:(nullable NSString *)name CLS_DEPRECATED("Please access this method via +sharedInstance");
|
||||
+ (void)setUserEmail:(nullable NSString *)email CLS_DEPRECATED("Please access this method via +sharedInstance");
|
||||
|
||||
/**
|
||||
* Set a value for a for a key to be associated with your crash data which will be visible in the Crashlytics UI.
|
||||
* When setting an object value, the object is converted to a string. This is typically done by calling
|
||||
* -[NSObject description].
|
||||
*
|
||||
* @param value The object to be associated with the key
|
||||
* @param key The key with which to associate the value
|
||||
*/
|
||||
- (void)setObjectValue:(nullable id)value forKey:(NSString *)key;
|
||||
|
||||
/**
|
||||
* Set an int value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
|
||||
*
|
||||
* @param value The integer value to be set
|
||||
* @param key The key with which to associate the value
|
||||
*/
|
||||
- (void)setIntValue:(int)value forKey:(NSString *)key;
|
||||
|
||||
/**
|
||||
* Set an BOOL value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
|
||||
*
|
||||
* @param value The BOOL value to be set
|
||||
* @param key The key with which to associate the value
|
||||
*/
|
||||
- (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
|
||||
|
||||
/**
|
||||
* Set an float value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
|
||||
*
|
||||
* @param value The float value to be set
|
||||
* @param key The key with which to associate the value
|
||||
*/
|
||||
- (void)setFloatValue:(float)value forKey:(NSString *)key;
|
||||
|
||||
+ (void)setObjectValue:(nullable id)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
|
||||
+ (void)setIntValue:(int)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
|
||||
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
|
||||
+ (void)setFloatValue:(float)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
|
||||
|
||||
/**
|
||||
* This method can be used to record a single exception structure in a report. This is particularly useful
|
||||
* when your code interacts with non-native languages like Lua, C#, or Javascript. This call can be
|
||||
* expensive and should only be used shortly before process termination. This API is not intended be to used
|
||||
* to log NSException objects. All safely-reportable NSExceptions are automatically captured by
|
||||
* Crashlytics.
|
||||
*
|
||||
* @param name The name of the custom exception
|
||||
* @param reason The reason this exception occurred
|
||||
* @param frameArray An array of CLSStackFrame objects
|
||||
*/
|
||||
- (void)recordCustomExceptionName:(NSString *)name reason:(nullable NSString *)reason frameArray:(CLS_GENERIC_NSARRAY(CLSStackFrame *) *)frameArray;
|
||||
|
||||
/**
|
||||
*
|
||||
* This allows you to record a non-fatal event, described by an NSError object. These events will be grouped and
|
||||
* displayed similarly to crashes. Keep in mind that this method can be expensive. Also, the total number of
|
||||
* NSErrors that can be recorded during your app's life-cycle is limited by a fixed-size circular buffer. If the
|
||||
* buffer is overrun, the oldest data is dropped. Errors are relayed to Crashlytics on a subsequent launch
|
||||
* of your application.
|
||||
*
|
||||
* You can also use the -recordError:withAdditionalUserInfo: to include additional context not represented
|
||||
* by the NSError instance itself.
|
||||
*
|
||||
**/
|
||||
- (void)recordError:(NSError *)error;
|
||||
- (void)recordError:(NSError *)error withAdditionalUserInfo:(nullable CLS_GENERIC_NSDICTIONARY(NSString *, id) *)userInfo;
|
||||
|
||||
- (void)logEvent:(NSString *)eventName CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
|
||||
- (void)logEvent:(NSString *)eventName attributes:(nullable NSDictionary *) attributes CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
|
||||
+ (void)logEvent:(NSString *)eventName CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
|
||||
+ (void)logEvent:(NSString *)eventName attributes:(nullable NSDictionary *) attributes CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
*
|
||||
* The CrashlyticsDelegate protocol provides a mechanism for your application to take
|
||||
* action on events that occur in the Crashlytics crash reporting system. You can make
|
||||
* use of these calls by assigning an object to the Crashlytics' delegate property directly,
|
||||
* or through the convenience +startWithAPIKey:delegate: method.
|
||||
*
|
||||
*/
|
||||
@protocol CrashlyticsDelegate <NSObject>
|
||||
@optional
|
||||
|
||||
|
||||
- (void)crashlyticsDidDetectCrashDuringPreviousExecution:(Crashlytics *)crashlytics CLS_DEPRECATED("Please refer to -crashlyticsDidDetectReportForLastExecution:");
|
||||
- (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id <CLSCrashReport>)crash CLS_DEPRECATED("Please refer to -crashlyticsDidDetectReportForLastExecution:");
|
||||
|
||||
/**
|
||||
*
|
||||
* Called when a Crashlytics instance has determined that the last execution of the
|
||||
* application resulted in a saved report. This is called synchronously on Crashlytics
|
||||
* initialization. Your delegate must invoke the completionHandler, but does not need to do so
|
||||
* synchronously, or even on the main thread. Invoking completionHandler with NO will cause the
|
||||
* detected report to be deleted and not submitted to Crashlytics. This is useful for
|
||||
* implementing permission prompts, or other more-complex forms of logic around submitting crashes.
|
||||
*
|
||||
* Instead of using this method, you should try to make use of -crashlyticsDidDetectReportForLastExecution:
|
||||
* if you can.
|
||||
*
|
||||
* @warning Failure to invoke the completionHandler will prevent submissions from being reported. Watch out.
|
||||
*
|
||||
* @warning Just implementing this delegate method will disable all forms of synchronous report submission. This can
|
||||
* impact the reliability of reporting crashes very early in application launch.
|
||||
*
|
||||
* @param report The CLSReport object representing the last detected report
|
||||
* @param completionHandler The completion handler to call when your logic has completed.
|
||||
*
|
||||
*/
|
||||
- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL submit))completionHandler;
|
||||
|
||||
/**
|
||||
*
|
||||
* Called when a Crashlytics instance has determined that the last execution of the
|
||||
* application resulted in a saved report. This method differs from
|
||||
* -crashlyticsDidDetectReportForLastExecution:completionHandler: in three important ways:
|
||||
*
|
||||
* - it is not called synchronously during initialization
|
||||
* - it does not give you the ability to prevent the report from being submitted
|
||||
* - the report object itself is immutable
|
||||
*
|
||||
* Thanks to these limitations, making use of this method does not impact reporting
|
||||
* reliabilty in any way.
|
||||
*
|
||||
* @param report The read-only CLSReport object representing the last detected report
|
||||
*
|
||||
*/
|
||||
|
||||
- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report;
|
||||
|
||||
/**
|
||||
* If your app is running on an OS that supports it (OS X 10.9+, iOS 7.0+), Crashlytics will submit
|
||||
* most reports using out-of-process background networking operations. This results in a significant
|
||||
* improvement in reliability of reporting, as well as power and performance wins for your users.
|
||||
* If you don't want this functionality, you can disable by returning NO from this method.
|
||||
*
|
||||
* @warning Background submission is not supported for extensions on iOS or OS X.
|
||||
*
|
||||
* @param crashlytics The Crashlytics singleton instance
|
||||
*
|
||||
* @return Return NO if you don't want out-of-process background network operations.
|
||||
*
|
||||
*/
|
||||
- (BOOL)crashlyticsCanUseBackgroundSessions:(Crashlytics *)crashlytics;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* `CrashlyticsKit` can be used as a parameter to `[Fabric with:@[CrashlyticsKit]];` in Objective-C. In Swift, use Crashlytics.sharedInstance()
|
||||
*/
|
||||
#define CrashlyticsKit [Crashlytics sharedInstance]
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
Binary file not shown.
|
@ -1,14 +0,0 @@
|
|||
framework module Crashlytics {
|
||||
header "Crashlytics.h"
|
||||
header "Answers.h"
|
||||
header "ANSCompatibility.h"
|
||||
header "CLSLogging.h"
|
||||
header "CLSReport.h"
|
||||
header "CLSStackFrame.h"
|
||||
header "CLSAttributes.h"
|
||||
|
||||
export *
|
||||
|
||||
link "z"
|
||||
link "c++"
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# run
|
||||
#
|
||||
# Copyright (c) 2015 Crashlytics. All rights reserved.
|
||||
#
|
||||
#
|
||||
# This script is meant to be run as a Run Script in the "Build Phases" section
|
||||
# of your Xcode project. It sends debug symbols to symbolicate stacktraces,
|
||||
# sends build events to track versions, and onboard apps for Crashlytics.
|
||||
#
|
||||
# This script calls upload-symbols twice:
|
||||
#
|
||||
# 1) First it calls upload-symbols synchronously in "validation" mode. If the
|
||||
# script finds issues with the build environment, it will report errors to Xcode.
|
||||
# In validation mode it exits before doing any time consuming work.
|
||||
#
|
||||
# 2) Then it calls upload-symbols in the background to actually send the build
|
||||
# event and upload symbols. It does this in the background so that it doesn't
|
||||
# slow down your builds. If an error happens here, you won't see it in Xcode.
|
||||
#
|
||||
# You can find the output for the background execution in Console.app, by
|
||||
# searching for "upload-symbols".
|
||||
#
|
||||
# If you want verbose output, you can pass the --debug flag to this script
|
||||
#
|
||||
|
||||
# Figure out where we're being called from
|
||||
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
|
||||
# If the first argument is specified without a dash, treat it as the Fabric API
|
||||
# Key and add it as an argument
|
||||
if [ -z "$1" ] || [[ $1 == -* ]]; then
|
||||
API_KEY_ARG=""
|
||||
else
|
||||
API_KEY_ARG="-a $1"; shift
|
||||
fi
|
||||
|
||||
# If a second argument is specified without a dash, treat it as the Build Secret
|
||||
# and add it as an argument
|
||||
if [ -z "$1" ] || [[ $1 == -* ]]; then
|
||||
BUILD_SECRET_ARG=""
|
||||
else
|
||||
BUILD_SECRET_ARG="-bs $1"; shift
|
||||
fi
|
||||
|
||||
# Build up the arguments list, passing through any flags added after the
|
||||
# API Key and Build Secret
|
||||
ARGUMENTS="$API_KEY_ARG $BUILD_SECRET_ARG $@"
|
||||
VALIDATE_ARGUMENTS="$ARGUMENTS --build-phase --validate"
|
||||
UPLOAD_ARGUMENTS="$ARGUMENTS --build-phase"
|
||||
|
||||
# Quote the path to handle folders with special characters
|
||||
COMMAND_PATH="\"$DIR/upload-symbols\" "
|
||||
|
||||
# Ensure params are as expected, run in sync mode to validate,
|
||||
# and cause a build error if validation fails
|
||||
eval $COMMAND_PATH$VALIDATE_ARGUMENTS
|
||||
return_code=$?
|
||||
|
||||
if [[ $return_code != 0 ]]; then
|
||||
exit $return_code
|
||||
fi
|
||||
|
||||
# Verification passed, convert and upload cSYMs in the background to prevent
|
||||
# build delays
|
||||
#
|
||||
# Note: Validation is performed again at this step before upload
|
||||
#
|
||||
# Note: Output can still be found in Console.app, by searching for
|
||||
# "upload-symbols"
|
||||
#
|
||||
eval $COMMAND_PATH$UPLOAD_ARGUMENTS > /dev/null 2>&1 &
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
We've now combined all our supported platforms into a single podspec. As a result, we moved our run script to a new location for Cocoapods projects: ${PODS_ROOT}/Fabric/run. To avoid breaking builds that reference the old location of the run script, we've placed this dummy script that calls to the correct location, while providing a helpful warning in Xcode if it is invoked. This bridge for backwards compatibility will be removed in a future release, so please heed the warning!
|
|
@ -1,6 +0,0 @@
|
|||
if [[ -z $PODS_ROOT ]]; then
|
||||
echo "error: The run binary delivered by cocoapods is in a new location, under '$"{"PODS_ROOT"}"/Fabric/run'. This script was put in place for backwards compatibility, but it relies on PODS_ROOT, which does not have a value in your current setup. Please update the path to the run binary to fix this issue."
|
||||
else
|
||||
echo "warning: The run script is now located at '$"{"PODS_ROOT"}"/Fabric/run'. To remove this warning, update your Run Script Build Phase to point to this new location."
|
||||
sh "${PODS_ROOT}/Fabric/run" "$@"
|
||||
fi
|
|
@ -1,27 +0,0 @@
|
|||
|
||||
# Fabric
|
||||
|
||||
## Overview
|
||||
|
||||
[Fabric](https://get.fabric.io) provides developers with the tools they need to build the best apps. Developed and maintained by Google and the team that built Crashlytics.
|
||||
|
||||
For a full list of SDKs provided through Fabric visit [https://fabric.io/kits](https://fabric.io/kits).
|
||||
|
||||
To follow the migration to Firebase, check out the [Fabric Roadmap](https://get.fabric.io/roadmap).
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
Fabric is a dependency for the Crashlytics SDK. To start using Crashlytics, there are two options:
|
||||
|
||||
1) The recommended way is to go to the [Firebase Crashlytics Docs](https://firebase.google.com/docs/crashlytics/get-started?platform=ios) and follow the directions there.
|
||||
|
||||
2) If you aren't using Firebase yet, go to [Fabric Kits](https://fabric.io/kits), and follow the directions for Crashlytics.
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
* [Documentation](https://docs.fabric.io/)
|
||||
* [Forums](https://stackoverflow.com/questions/tagged/google-fabric)
|
||||
* [Website](https://get.fabric.io)
|
||||
* Follow us on Twitter: [@fabric](https://twitter.com/fabric)
|
Binary file not shown.
|
@ -1,51 +0,0 @@
|
|||
//
|
||||
// FABAttributes.h
|
||||
// Fabric
|
||||
//
|
||||
// Copyright (C) 2015 Twitter, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#define FAB_UNAVAILABLE(x) __attribute__((unavailable(x)))
|
||||
|
||||
#if !__has_feature(nullability)
|
||||
#define nonnull
|
||||
#define nullable
|
||||
#define _Nullable
|
||||
#define _Nonnull
|
||||
#endif
|
||||
|
||||
#ifndef NS_ASSUME_NONNULL_BEGIN
|
||||
#define NS_ASSUME_NONNULL_BEGIN
|
||||
#endif
|
||||
|
||||
#ifndef NS_ASSUME_NONNULL_END
|
||||
#define NS_ASSUME_NONNULL_END
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* The following macros are defined here to provide
|
||||
* backwards compatability. If you are still using
|
||||
* them you should migrate to the native nullability
|
||||
* macros.
|
||||
*/
|
||||
#define fab_nullable nullable
|
||||
#define fab_nonnull nonnull
|
||||
#define FAB_NONNULL __fab_nonnull
|
||||
#define FAB_NULLABLE __fab_nullable
|
||||
#define FAB_START_NONNULL NS_ASSUME_NONNULL_BEGIN
|
||||
#define FAB_END_NONNULL NS_ASSUME_NONNULL_END
|
|
@ -1,82 +0,0 @@
|
|||
//
|
||||
// Fabric.h
|
||||
// Fabric
|
||||
//
|
||||
// Copyright (C) 2015 Twitter, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "FABAttributes.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000
|
||||
#error "Fabric's minimum iOS version is 6.0"
|
||||
#endif
|
||||
#else
|
||||
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1070
|
||||
#error "Fabric's minimum OS X version is 10.7"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Fabric Base. Coordinates configuration and starts all provided kits.
|
||||
*/
|
||||
@interface Fabric : NSObject
|
||||
|
||||
/**
|
||||
* Initialize Fabric and all provided kits. Call this method within your App Delegate's `application:didFinishLaunchingWithOptions:` and provide the kits you wish to use.
|
||||
*
|
||||
* For example, in Objective-C:
|
||||
*
|
||||
* `[Fabric with:@[[Crashlytics class], [Twitter class], [Digits class], [MoPub class]]];`
|
||||
*
|
||||
* Swift:
|
||||
*
|
||||
* `Fabric.with([Crashlytics.self(), Twitter.self(), Digits.self(), MoPub.self()])`
|
||||
*
|
||||
* Only the first call to this method is honored. Subsequent calls are no-ops.
|
||||
*
|
||||
* @param kitClasses An array of kit Class objects
|
||||
*
|
||||
* @return Returns the shared Fabric instance. In most cases this can be ignored.
|
||||
*/
|
||||
+ (instancetype)with:(NSArray *)kitClasses;
|
||||
|
||||
/**
|
||||
* Returns the Fabric singleton object.
|
||||
*/
|
||||
+ (instancetype)sharedSDK;
|
||||
|
||||
/**
|
||||
* This BOOL enables or disables debug logging, such as kit version information. The default value is NO.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL debug;
|
||||
|
||||
/**
|
||||
* Unavailable. Use `+sharedSDK` to retrieve the shared Fabric instance.
|
||||
*/
|
||||
- (id)init FAB_UNAVAILABLE("Use +sharedSDK to retrieve the shared Fabric instance.");
|
||||
|
||||
/**
|
||||
* Unavailable. Use `+sharedSDK` to retrieve the shared Fabric instance.
|
||||
*/
|
||||
+ (instancetype)new FAB_UNAVAILABLE("Use +sharedSDK to retrieve the shared Fabric instance.");
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
Binary file not shown.
|
@ -1,6 +0,0 @@
|
|||
framework module Fabric {
|
||||
umbrella header "Fabric.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# run
|
||||
#
|
||||
# Copyright (c) 2015 Crashlytics. All rights reserved.
|
||||
#
|
||||
#
|
||||
# This script is meant to be run as a Run Script in the "Build Phases" section
|
||||
# of your Xcode project. It sends debug symbols to symbolicate stacktraces,
|
||||
# sends build events to track versions, and onboard apps for Crashlytics.
|
||||
#
|
||||
# This script calls upload-symbols twice:
|
||||
#
|
||||
# 1) First it calls upload-symbols synchronously in "validation" mode. If the
|
||||
# script finds issues with the build environment, it will report errors to Xcode.
|
||||
# In validation mode it exits before doing any time consuming work.
|
||||
#
|
||||
# 2) Then it calls upload-symbols in the background to actually send the build
|
||||
# event and upload symbols. It does this in the background so that it doesn't
|
||||
# slow down your builds. If an error happens here, you won't see it in Xcode.
|
||||
#
|
||||
# You can find the output for the background execution in Console.app, by
|
||||
# searching for "upload-symbols".
|
||||
#
|
||||
# If you want verbose output, you can pass the --debug flag to this script
|
||||
#
|
||||
|
||||
# Figure out where we're being called from
|
||||
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
|
||||
# If the first argument is specified without a dash, treat it as the Fabric API
|
||||
# Key and add it as an argument
|
||||
if [ -z "$1" ] || [[ $1 == -* ]]; then
|
||||
API_KEY_ARG=""
|
||||
else
|
||||
API_KEY_ARG="-a $1"; shift
|
||||
fi
|
||||
|
||||
# If a second argument is specified without a dash, treat it as the Build Secret
|
||||
# and add it as an argument
|
||||
if [ -z "$1" ] || [[ $1 == -* ]]; then
|
||||
BUILD_SECRET_ARG=""
|
||||
else
|
||||
BUILD_SECRET_ARG="-bs $1"; shift
|
||||
fi
|
||||
|
||||
# Build up the arguments list, passing through any flags added after the
|
||||
# API Key and Build Secret
|
||||
ARGUMENTS="$API_KEY_ARG $BUILD_SECRET_ARG $@"
|
||||
VALIDATE_ARGUMENTS="$ARGUMENTS --build-phase --validate"
|
||||
UPLOAD_ARGUMENTS="$ARGUMENTS --build-phase"
|
||||
|
||||
# Quote the path to handle folders with special characters
|
||||
COMMAND_PATH="\"$DIR/upload-symbols\" "
|
||||
|
||||
# Ensure params are as expected, run in sync mode to validate,
|
||||
# and cause a build error if validation fails
|
||||
eval $COMMAND_PATH$VALIDATE_ARGUMENTS
|
||||
return_code=$?
|
||||
|
||||
if [[ $return_code != 0 ]]; then
|
||||
exit $return_code
|
||||
fi
|
||||
|
||||
# Verification passed, convert and upload cSYMs in the background to prevent
|
||||
# build delays
|
||||
#
|
||||
# Note: Validation is performed again at this step before upload
|
||||
#
|
||||
# Note: Output can still be found in Console.app, by searching for
|
||||
# "upload-symbols"
|
||||
#
|
||||
eval $COMMAND_PATH$UPLOAD_ARGUMENTS > /dev/null 2>&1 &
|
Binary file not shown.
|
@ -1,73 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# run
|
||||
#
|
||||
# Copyright (c) 2015 Crashlytics. All rights reserved.
|
||||
#
|
||||
#
|
||||
# This script is meant to be run as a Run Script in the "Build Phases" section
|
||||
# of your Xcode project. It sends debug symbols to symbolicate stacktraces,
|
||||
# sends build events to track versions, and onboard apps for Crashlytics.
|
||||
#
|
||||
# This script calls upload-symbols twice:
|
||||
#
|
||||
# 1) First it calls upload-symbols synchronously in "validation" mode. If the
|
||||
# script finds issues with the build environment, it will report errors to Xcode.
|
||||
# In validation mode it exits before doing any time consuming work.
|
||||
#
|
||||
# 2) Then it calls upload-symbols in the background to actually send the build
|
||||
# event and upload symbols. It does this in the background so that it doesn't
|
||||
# slow down your builds. If an error happens here, you won't see it in Xcode.
|
||||
#
|
||||
# You can find the output for the background execution in Console.app, by
|
||||
# searching for "upload-symbols".
|
||||
#
|
||||
# If you want verbose output, you can pass the --debug flag to this script
|
||||
#
|
||||
|
||||
# Figure out where we're being called from
|
||||
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
|
||||
# If the first argument is specified without a dash, treat it as the Fabric API
|
||||
# Key and add it as an argument
|
||||
if [ -z "$1" ] || [[ $1 == -* ]]; then
|
||||
API_KEY_ARG=""
|
||||
else
|
||||
API_KEY_ARG="-a $1"; shift
|
||||
fi
|
||||
|
||||
# If a second argument is specified without a dash, treat it as the Build Secret
|
||||
# and add it as an argument
|
||||
if [ -z "$1" ] || [[ $1 == -* ]]; then
|
||||
BUILD_SECRET_ARG=""
|
||||
else
|
||||
BUILD_SECRET_ARG="-bs $1"; shift
|
||||
fi
|
||||
|
||||
# Build up the arguments list, passing through any flags added after the
|
||||
# API Key and Build Secret
|
||||
ARGUMENTS="$API_KEY_ARG $BUILD_SECRET_ARG $@"
|
||||
VALIDATE_ARGUMENTS="$ARGUMENTS --build-phase --validate"
|
||||
UPLOAD_ARGUMENTS="$ARGUMENTS --build-phase"
|
||||
|
||||
# Quote the path to handle folders with special characters
|
||||
COMMAND_PATH="\"$DIR/upload-symbols\" "
|
||||
|
||||
# Ensure params are as expected, run in sync mode to validate,
|
||||
# and cause a build error if validation fails
|
||||
eval $COMMAND_PATH$VALIDATE_ARGUMENTS
|
||||
return_code=$?
|
||||
|
||||
if [[ $return_code != 0 ]]; then
|
||||
exit $return_code
|
||||
fi
|
||||
|
||||
# Verification passed, convert and upload cSYMs in the background to prevent
|
||||
# build delays
|
||||
#
|
||||
# Note: Validation is performed again at this step before upload
|
||||
#
|
||||
# Note: Output can still be found in Console.app, by searching for
|
||||
# "upload-symbols"
|
||||
#
|
||||
eval $COMMAND_PATH$UPLOAD_ARGUMENTS > /dev/null 2>&1 &
|
Binary file not shown.
|
@ -2,14 +2,20 @@
|
|||
[![License](https://img.shields.io/cocoapods/l/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase)
|
||||
[![Platform](https://img.shields.io/cocoapods/p/Firebase.svg?style=flat)](https://cocoapods.org/pods/Firebase)
|
||||
|
||||
[![Actions Status][gh-abtesting-badge]][gh-actions]
|
||||
[![Actions Status][gh-auth-badge]][gh-actions]
|
||||
[![Actions Status][gh-core-badge]][gh-actions]
|
||||
[![Actions Status][gh-crashlytics-badge]][gh-actions]
|
||||
[![Actions Status][gh-database-badge]][gh-actions]
|
||||
[![Actions Status][gh-datatransport-badge]][gh-actions]
|
||||
[![Actions Status][gh-dynamiclinks-badge]][gh-actions]
|
||||
[![Actions Status][gh-firebasepod-badge]][gh-actions]
|
||||
[![Actions Status][gh-firestore-badge]][gh-actions]
|
||||
[![Actions Status][gh-functions-badge]][gh-actions]
|
||||
[![Actions Status][gh-inappmessaging-badge]][gh-actions]
|
||||
[![Actions Status][gh-interop-badge]][gh-actions]
|
||||
[![Actions Status][gh-messaging-badge]][gh-actions]
|
||||
[![Actions Status][gh-remoteconfig-badge]][gh-actions]
|
||||
[![Actions Status][gh-storage-badge]][gh-actions]
|
||||
[![Actions Status][gh-symbolcollision-badge]][gh-actions]
|
||||
[![Actions Status][gh-zip-badge]][gh-actions]
|
||||
|
@ -92,7 +98,7 @@ Instructions for installing binary frameworks via
|
|||
To develop Firebase software in this repository, ensure that you have at least
|
||||
the following software:
|
||||
|
||||
* Xcode 10.1 (or later)
|
||||
* Xcode 10.3 (or later)
|
||||
* CocoaPods 1.7.2 (or later)
|
||||
* [CocoaPods generate](https://github.com/square/cocoapods-generate)
|
||||
|
||||
|
@ -123,6 +129,10 @@ Firestore has a self contained Xcode project. See
|
|||
|
||||
See [AddNewPod.md](AddNewPod.md).
|
||||
|
||||
### Managing Headers and Imports
|
||||
|
||||
See [HeadersImports.md](HeadersImports.md).
|
||||
|
||||
### Code Formatting
|
||||
|
||||
To ensure that the code is formatted consistently, run the script
|
||||
|
@ -185,8 +195,16 @@ building and running the FirebaseAuth pod along with various samples and tests.
|
|||
|
||||
### Firebase Database
|
||||
|
||||
To run the Database Integration tests, make your database authentication rules
|
||||
[public](https://firebase.google.com/docs/database/security/quickstart).
|
||||
The Firebase Database Integration tests can be run against a locally running Database Emulator
|
||||
or against a production instance.
|
||||
|
||||
To run against a local emulator instance, invoke `./scripts/run_database_emulator.sh start` before
|
||||
running the integration test.
|
||||
|
||||
To run against a production instance, provide a valid GoogleServices-Info.plist and copy it to
|
||||
`Example/Database/App/GoogleService-Info.plist`. Your Security Rule must be set to
|
||||
[public](https://firebase.google.com/docs/database/security/quickstart) while your tests are
|
||||
running.
|
||||
|
||||
### Firebase Storage
|
||||
|
||||
|
@ -274,14 +292,20 @@ Your use of Firebase is governed by the
|
|||
[Terms of Service for Firebase Services](https://firebase.google.com/terms/).
|
||||
|
||||
[gh-actions]: https://github.com/firebase/firebase-ios-sdk/actions
|
||||
[gh-abtesting-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/abtesting/badge.svg
|
||||
[gh-auth-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/auth/badge.svg
|
||||
[gh-core-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/core/badge.svg
|
||||
[gh-crashlytics-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/crashlytics/badge.svg
|
||||
[gh-database-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/database/badge.svg
|
||||
[gh-datatransport-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/datatransport/badge.svg
|
||||
[gh-dynamiclinks-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/dynamiclinks/badge.svg
|
||||
[gh-firebasepod-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/firebasepod/badge.svg
|
||||
[gh-firestore-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/firestore/badge.svg
|
||||
[gh-functions-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/functions/badge.svg
|
||||
[gh-inappmessaging-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/inappmessaging/badge.svg
|
||||
[gh-interop-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/interop/badge.svg
|
||||
[gh-messaging-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/messaging/badge.svg
|
||||
[gh-remoteconfig-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/remoteconfig/badge.svg
|
||||
[gh-storage-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/storage/badge.svg
|
||||
[gh-symbolcollision-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/symbolcollision/badge.svg
|
||||
[gh-zip-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/zip/badge.svg
|
||||
|
|
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue