Compare commits

...

20 Commits

Author SHA1 Message Date
Mattermost Build
f325cd8168 Bump app build number to 374 (#5741) (#5742)
(cherry picked from commit 5e0c75d772)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-10-10 11:58:49 -03:00
Mattermost Build
9cfae7ab3e Fix send message with physical keyboard on iPad 15 (#5737) (#5738)
(cherry picked from commit 16583c2a3b)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-10-10 11:42:43 -03:00
Mattermost Build
992adad5a1 Bump app build number to 373 (#5739) (#5740)
(cherry picked from commit 93dbe4af9e)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-10-10 11:14:34 -03:00
Mattermost Build
e73d9cb105 Bump app build number to 372 (#5734) (#5735)
(cherry picked from commit 42071314d3)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-10-08 10:01:08 -03:00
Mattermost Build
db6109920e Update Readme intro and add logo (#5729) (#5733)
* Update intro and add logo

* Update metadata to new messaging

* Update README.md

Co-authored-by: Amy Blais <29708087+amyblais@users.noreply.github.com>

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
Co-authored-by: Amy Blais <29708087+amyblais@users.noreply.github.com>
(cherry picked from commit 6586d1b283)

Co-authored-by: Ben Lloyd Pearson <ben.pearson@mattermost.com>
2021-10-08 09:43:14 -03:00
Mattermost Build
c0ad0bf12a Local only collapse state for mobile (#5721) (#5732)
(cherry picked from commit a3c00f59d6)

Co-authored-by: Shaz Amjad <shaz.amjad@mattermost.com>
2021-10-08 09:38:57 -03:00
Mattermost Build
7538ad2c6f Hides favourites category if empty (#5722) (#5731)
(cherry picked from commit 000caf09e9)

Co-authored-by: Shaz Amjad <shaz.amjad@mattermost.com>
2021-10-08 09:38:46 -03:00
Mattermost Build
546487aa08 Cancel button restored (#5725) (#5730)
(cherry picked from commit bd74310f29)

Co-authored-by: Shaz Amjad <shaz.amjad@mattermost.com>
2021-10-08 09:38:34 -03:00
Mattermost Build
9c2611bf4b MM-39054 patch Android react-native-file-viewer (#5717) (#5724)
(cherry picked from commit f1c3538283)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-10-06 10:44:32 -03:00
Mattermost Build
2135101a01 Translated using Weblate (German) (#5719) (#5720)
Currently translated at 100.0% (773 of 773 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/de/

Co-authored-by: JtheBAB <srast@bioc.uzh.ch>
(cherry picked from commit 7dd3fbb75d)

Co-authored-by: Weblate (bot) <hosted@weblate.org>
2021-10-06 09:38:20 +02:00
Mattermost Build
828d357de0 Bump app build number to 371 (#5715) (#5716)
(cherry picked from commit e301c9d5d1)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-10-04 15:57:06 -03:00
Mattermost Build
b4709846cc Translations update from Weblate (#5713) (#5714)
* Translated using Weblate (Swedish)

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/sv/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/es/

Co-authored-by: MArtin Johnson <martinjohnson@bahnhof.se>
Co-authored-by: Elias  Nahum <elias@mattermost.com>
(cherry picked from commit 0332b52ee2)

Co-authored-by: Weblate (bot) <hosted@weblate.org>
2021-10-04 15:29:27 -03:00
Mattermost Build
6ddb9be41f update dependencies (#5706) (#5709)
(cherry picked from commit a28a3826fa)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-10-01 11:55:11 -03:00
Mattermost Build
6a1b8a481b Translations update from Weblate (#5694) (#5705)
* Translated using Weblate (German)

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/de/

* Translated using Weblate (French)

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/fr/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/nl/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/zh_Hans/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/hu/

* Translated using Weblate (English (Australia))

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/en_AU/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/ja/

* Translated using Weblate (Polish)

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/pl/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/tr/

Translated using Weblate (Turkish)

Currently translated at 100.0% (771 of 771 strings)

Translation: mattermost-languages-shipped/mattermost-mobile
Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-mobile_master/tr/

Co-authored-by: JtheBAB <srast@bioc.uzh.ch>
Co-authored-by: Nathanaël <contact@nathanaelhoun.fr>
Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
Co-authored-by: aeomin <lin@aeomin.net>
Co-authored-by: Tóth Csaba // Online ERP Hungary Kft <csaba.toth@online-erp.hu>
Co-authored-by: Matthew Williams <Matthew.Williams@outlook.com.au>
Co-authored-by: kaakaa <stooner.hoe@gmail.com>
Co-authored-by: master7 <marcin.karkosz@rajska.info>
Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
(cherry picked from commit 8447d7feea)

Co-authored-by: Weblate (bot) <hosted@weblate.org>
2021-09-30 10:19:43 -03:00
Mattermost Build
c94716353c Use @mattermost/react-native-paste-input to allow pasting of images & files (#5703) (#5704)
* Use @mattermost/react-native-paste-input to allow pasting of images & files

* upgrade @mattermost/react-native-paste-input

(cherry picked from commit 523777a207)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-09-30 09:50:16 -03:00
Mattermost Build
38bddbf9fd Bump app build number to 370 (#5700) (#5701)
(cherry picked from commit d9e8b3e08e)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-09-28 11:50:15 -03:00
Mattermost Build
e57f144543 [MM-38683] Android Initialisation Bug + BottomSheet Correction (#5685) (#5699)
* Fixes various init bugs for android

* Ignores Type Error

* MM-38683: Removes 'cancel' button.

* MM-38683: Removes some 'hideCancel' props.

* Android bottom margin

* Update options modal style

Co-authored-by: Martin Kraft <martin@upspin.org>
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
(cherry picked from commit b36dbf9b34)

Co-authored-by: Shaz Amjad <shaz.amjad@mattermost.com>
2021-09-28 11:35:05 -03:00
Mattermost Build
3319f7ccd0 Fix android extension crash (#5696) (#5698)
(cherry picked from commit 7e9c574c3f)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-09-28 11:33:55 -03:00
Mattermost Build
95ff84f24a Update NOTICE.txt (#5688) (#5689) 2021-09-22 15:36:45 -03:00
Mattermost Build
49f86826e1 Update dependencies (#5686) (#5687)
* Update dependencies

* Fix unsigned builds

(cherry picked from commit ada6be9b7a)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-09-22 14:00:45 -03:00
155 changed files with 12871 additions and 24508 deletions

View File

@@ -14,7 +14,7 @@ executors:
NODE_ENV: production
BABEL_ENV: production
docker:
- image: circleci/android:api-29-node
- image: circleci/android:api-30-node
working_directory: ~/mattermost-mobile
resource_class: <<parameters.resource_class>>
@@ -24,7 +24,7 @@ executors:
NODE_ENV: production
BABEL_ENV: production
macos:
xcode: "12.1.0"
xcode: "13.0.0"
working_directory: ~/mattermost-mobile
shell: /bin/bash --login -o pipefail

View File

@@ -23,9 +23,6 @@ node_modules/react-native/flow/
[options]
emoji=true
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
exact_by_default=true
module.file_ext=.js
@@ -64,4 +61,4 @@ untyped-import
untyped-type-import
[version]
^0.137.0
^0.149.0

View File

@@ -532,6 +532,29 @@ SOFTWARE.
---
## @types/redux-mock-store
This product contains '@types/redux-mock-store' by Redux.
A mock store for testing Redux async action creators and middleware. The mock store will create an array of dispatched actions which serve as an action log for tests.
* HOMEPAGE:
* https://github.com/reduxjs/redux-mock-store
* LICENSE: MIT
The MIT License (MIT)
Copyright (c) 2017 Arnaud Benard
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
## analytics-react-native
This product contains a modified version of 'analytics-react-native' by Segment.

View File

@@ -1,14 +1,14 @@
# Mattermost Mobile
# Mattermost Mobile App
[![Mattermost](https://user-images.githubusercontent.com/7205829/136108314-75cd2e1f-4147-4cfa-a16c-9b3b0313ea25.png)](https://mattermost.com)
- **Minimum Server versions:** Current ESR version (5.37.0)
- **Supported iOS versions:** 11+
- **Supported iOS versions:** 12.1+
- **Supported Android versions:** 7.0+
Mattermost is an open source Slack-alternative used by thousands of companies around the world in 14 languages. Learn more at [https://about.mattermost.com](https://about.mattermost.com).
You can download our apps from the [App Store](https://about.mattermost.com/mattermost-ios-app/) or [Google Play Store](https://about.mattermost.com/mattermost-android-app/), or [build them yourself](https://developers.mattermost.com/contribute/mobile/build-your-own/).
[Mattermost](https://mattermost.com) is an open source platform for secure collaboration across the entire software development lifecycle. This repo is for the mobile app that runs on Android and iOS. You can download our apps from the [App Store](https://about.mattermost.com/mattermost-ios-app/) or [Google Play Store](https://about.mattermost.com/mattermost-android-app/), or [build them yourself](https://developers.mattermost.com/contribute/mobile/build-your-own/).
We plan on releasing monthly updates with new features - check the [changelog](https://github.com/mattermost/mattermost-mobile/blob/master/CHANGELOG.md) for what features are currently supported!
New features are released monthly - check the [changelog](https://github.com/mattermost/mattermost-mobile/blob/master/CHANGELOG.md) for currently-supported features!
**Important:** If you self-compile the Mattermost Mobile apps you also need to deploy your own [Mattermost Push Notification Service](https://github.com/mattermost/mattermost-push-proxy/releases).

View File

@@ -122,17 +122,12 @@ def enableHermes = project.ext.react.get("enableHermes", false);
android {
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId "com.mattermost.rnbeta"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
versionCode 369
versionCode 374
versionName "1.47.0"
multiDexEnabled = true
testBuildType System.getProperty('testBuildType', 'debug')
@@ -269,7 +264,7 @@ dependencies {
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
from configurations.implementation
into 'libs'
}

View File

@@ -57,7 +57,6 @@ private final ReactNativeHost mReactNativeHost =
// Packages that cannot be auto linked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new RNNotificationsPackage(MainApplication.this));
packages.add(new RNPasteableTextInputPackage());
packages.add(
new TurboReactPackage() {
@Override

View File

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

View File

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

View File

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

View File

@@ -1,163 +0,0 @@
package com.mattermost.rnbeta;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import android.os.Build;
import android.util.Patterns;
import android.webkit.MimeTypeMap;
import android.webkit.URLUtil;
import androidx.annotation.RequiresApi;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.mattermost.share.RealPathUtil;
import com.mattermost.share.ShareModule;
import java.io.FileNotFoundException;
import java.io.File;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Matcher;
public class RNPasteableEditTextOnPasteListener implements RNEditTextOnPasteListener {
private final RNPasteableEditText mEditText;
RNPasteableEditTextOnPasteListener(RNPasteableEditText editText) {
mEditText = editText;
}
@Override
public void onPaste(Uri itemUri) {
ReactContext reactContext = (ReactContext)mEditText.getContext();
String uri = itemUri.toString();
WritableArray images = null;
WritableMap error = null;
String uriMimeType = reactContext.getContentResolver().getType(itemUri);
if (uriMimeType == null) {
return;
}
// Special handle for Google docs
if (uri.equals("content://com.google.android.apps.docs.editors.kix.editors.clipboard")) {
ClipboardManager clipboardManager = (ClipboardManager) reactContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboardManager.getPrimaryClip();
if (clipData == null) {
return;
}
ClipData.Item item = clipData.getItemAt(0);
String htmlText = item.getHtmlText();
// Find uri from html
Matcher matcher = Patterns.WEB_URL.matcher(htmlText);
if (matcher.find()) {
uri = htmlText.substring(matcher.start(1), matcher.end());
}
}
if (uri.startsWith("http")) {
Thread pastImageFromUrlThread = new Thread(new RNPasteableImageFromUrl(reactContext, mEditText, uri));
pastImageFromUrlThread.start();
return;
}
uri = RealPathUtil.getRealPathFromURI(reactContext, itemUri);
if (uri == null) {
return;
}
// Get type
String extension = MimeTypeMap.getFileExtensionFromUrl(uri);
if (extension == null) {
return;
}
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mimeType == null) {
return;
}
// Get fileName
String fileName = URLUtil.guessFileName(uri, null, mimeType);
if (uri.contains(ShareModule.CACHE_DIR_NAME) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
uri = moveToImagesCache(uri, fileName);
}
if (uri == null) {
return;
}
// Get fileSize
long fileSize;
try {
ContentResolver contentResolver = reactContext.getContentResolver();
AssetFileDescriptor assetFileDescriptor = contentResolver.openAssetFileDescriptor(itemUri, "r");
if (assetFileDescriptor == null) {
return;
}
fileSize = assetFileDescriptor.getLength();
WritableMap image = Arguments.createMap();
image.putString("type", mimeType);
image.putDouble("fileSize", fileSize);
image.putString("fileName", fileName);
image.putString("uri", "file://" + uri);
images = Arguments.createArray();
images.pushMap(image);
} catch (FileNotFoundException e) {
error = Arguments.createMap();
error.putString("message", e.getMessage());
}
WritableMap event = Arguments.createMap();
event.putArray("data", images);
event.putMap("error", error);
reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(
mEditText.getId(),
"onPaste",
event
);
}
@RequiresApi(api = Build.VERSION_CODES.O)
private String moveToImagesCache(String src, String fileName) {
ReactContext ctx = (ReactContext)mEditText.getContext();
String cacheFolder = ctx.getCacheDir().getAbsolutePath() + "/Images/";
String dest = cacheFolder + fileName;
File folder = new File(cacheFolder);
try {
if (!folder.exists()) {
boolean created = folder.mkdirs();
if (!created) {
return null;
}
}
Files.move(Paths.get(src), Paths.get(dest));
} catch (FileAlreadyExistsException fileError) {
// Do nothing and return dest path
} catch (Exception err) {
return null;
}
return dest;
}
}

View File

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

View File

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

View File

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

View File

@@ -2,15 +2,15 @@
buildscript {
ext {
buildToolsVersion = "29.0.3"
buildToolsVersion = "30.0.2"
minSdkVersion = 24
compileSdkVersion = 29
targetSdkVersion = 29
compileSdkVersion = 30
targetSdkVersion = 30
supportLibVersion = "28.0.0"
kotlinVersion = "1.3.61"
kotlinVersion = "1.5.30"
firebaseVersion = "21.0.0"
RNNKotlinVersion = kotlinVersion
ndkVersion = "21.1.6352462"
ndkVersion = "20.1.5948944"
}
repositories {
@@ -20,7 +20,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.android.tools.build:gradle:4.2.1'
classpath 'com.google.gms:google-services:4.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"

View File

@@ -30,4 +30,4 @@ android.useAndroidX=true
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.75.1
FLIPPER_VERSION=0.93.0

View File

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

View File

@@ -457,6 +457,13 @@ export function showOverlay(name, passProps, options = {}) {
overlay: {
interceptTouchOutside: false,
},
...Platform.select({
android: {
statusBar: {
drawBehind: true,
},
},
}),
};
Navigation.showOverlay({

View File

@@ -36,7 +36,7 @@ describe('Websocket Chanel Events', () => {
});
afterAll(async () => {
Actions.close()();
Actions.close();
mockServer.stop();
await TestHelper.tearDown();
});

View File

@@ -28,7 +28,7 @@ describe('Websocket General Events', () => {
});
afterAll(async () => {
Actions.close()();
Actions.close();
mockServer.stop();
await TestHelper.tearDown();
});

View File

@@ -26,7 +26,7 @@ describe('Websocket Integration Events', () => {
});
afterAll(async () => {
Actions.close()();
Actions.close();
mockServer.stop();
await TestHelper.tearDown();
});

View File

@@ -34,7 +34,7 @@ describe('Websocket Post Events', () => {
});
afterAll(async () => {
Actions.close()();
Actions.close();
mockServer.stop();
await TestHelper.tearDown();
});

View File

@@ -26,7 +26,7 @@ describe('Websocket Reaction Events', () => {
});
afterAll(async () => {
Actions.close()();
Actions.close();
mockServer.stop();
await TestHelper.tearDown();
});

View File

@@ -35,7 +35,7 @@ describe('Websocket Team Events', () => {
});
afterAll(async () => {
Actions.close()();
Actions.close();
mockServer.stop();
await TestHelper.tearDown();
});

View File

@@ -33,7 +33,7 @@ describe('Websocket User Events', () => {
});
afterAll(async () => {
Actions.close()();
Actions.close();
mockServer.stop();
await TestHelper.tearDown();
});

View File

@@ -64,7 +64,7 @@ describe('Actions.Websocket', () => {
});
afterAll(async () => {
Actions.close()();
Actions.close();
mockServer.stop();
await TestHelper.tearDown();
});
@@ -166,7 +166,7 @@ describe('Actions.Websocket doReconnect', () => {
});
afterAll(async () => {
Actions.close()();
Actions.close();
await TestHelper.tearDown();
});
@@ -373,7 +373,7 @@ describe('Actions.Websocket notVisibleUsersActions', () => {
const user4 = TestHelper.fakeUserWithId();
const user5 = TestHelper.fakeUserWithId();
it('should do nothing if the known users and the profiles list are the same', async () => {
it.skip('should do nothing if the known users and the profiles list are the same', async () => {
const profiles = {
[me.id]: me,
[user.id]: user,
@@ -397,7 +397,7 @@ describe('Actions.Websocket notVisibleUsersActions', () => {
expect(actions.length).toEqual(0);
});
it('should do nothing if there are known users in my memberships but not in the profiles list', async () => {
it.skip('should do nothing if there are known users in my memberships but not in the profiles list', async () => {
const profiles = {
[me.id]: me,
[user3.id]: user3,
@@ -419,7 +419,7 @@ describe('Actions.Websocket notVisibleUsersActions', () => {
expect(actions.length).toEqual(0);
});
it('should remove the users if there are unknown users in the profiles list', async () => {
it.skip('should remove the users if there are unknown users in the profiles list', async () => {
const profiles = {
[me.id]: me,
[user.id]: user,
@@ -502,7 +502,7 @@ describe('Actions.Websocket handleUserTypingEvent', () => {
const expectedActionsTypes = [
WebsocketEvents.TYPING,
UserTypes.RECEIVED_STATUSES,
'BATCHING_REDUCER.BATCH',
];
await testStore.dispatch(Actions.handleUserTypingEvent(msg));

View File

@@ -104,7 +104,6 @@ const ClientUsers = (superclass: any) => class extends superclass {
getKnownUsers = async () => {
analytics.trackAPI('api_get_known_users');
return this.doFetch(
`${this.getUsersRoute()}/known`,
{method: 'get'},

View File

@@ -113,7 +113,6 @@ export default class AtMention extends React.PureComponent {
BottomSheet.showBottomSheetWithOptions({
options: [actionText, cancelText],
cancelButtonIndex: 1,
}, (value) => {
if (value !== 1) {
this.handleCopyMention();

View File

@@ -232,7 +232,7 @@ export default class AttachmentButton extends PureComponent {
if (hasPermission) {
try {
const res = await DocumentPicker.pick({type: [browseFileTypes]});
const res = await DocumentPicker.pickSingle({type: [browseFileTypes]});
emmProvider.inBackgroundSince = null;
if (Platform.OS === 'android') {
// For android we need to retrieve the realPath in case the file being imported is from the cloud

View File

@@ -2341,7 +2341,6 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
"zzz",
]
}
disableVirtualization={false}
extraData={
Object {
"active": true,
@@ -4682,19 +4681,14 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
],
}
}
horizontal={false}
initialListSize={10}
initialNumToRender={10}
keyExtractor={[Function]}
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={10}
nestedScrollEnabled={false}
numColumns={1}
onEndReachedThreshold={2}
pageSize={10}
removeClippedSubviews={true}
renderItem={[Function]}
scrollEventThrottle={50}
style={
Array [
Object {
@@ -4708,7 +4702,5 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
]
}
testID="emoji_suggestion.list"
updateCellsBatchingPeriod={50}
windowSize={21}
/>
`;

View File

@@ -13,7 +13,6 @@ exports[`components/autocomplete/slash_suggestion should match snapshot 1`] = `
},
]
}
disableVirtualization={false}
extraData={
Object {
"active": true,
@@ -29,17 +28,12 @@ exports[`components/autocomplete/slash_suggestion should match snapshot 1`] = `
"lastCommandRequest": 1234,
}
}
horizontal={false}
initialNumToRender={10}
keyExtractor={[Function]}
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={10}
nestedScrollEnabled={false}
numColumns={1}
onEndReachedThreshold={2}
removeClippedSubviews={true}
renderItem={[Function]}
scrollEventThrottle={50}
style={
Array [
Object {
@@ -54,7 +48,5 @@ exports[`components/autocomplete/slash_suggestion should match snapshot 1`] = `
]
}
testID="slash_suggestion.list"
updateCellsBatchingPeriod={50}
windowSize={21}
/>
`;

View File

@@ -13,18 +13,12 @@ exports[`components/autocomplete/app_slash_suggestion should match snapshot 1`]
},
]
}
disableVirtualization={false}
horizontal={false}
initialNumToRender={10}
keyExtractor={[Function]}
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={10}
nestedScrollEnabled={false}
numColumns={1}
onEndReachedThreshold={2}
removeClippedSubviews={true}
renderItem={[Function]}
scrollEventThrottle={50}
style={
Array [
Object {
@@ -39,7 +33,5 @@ exports[`components/autocomplete/app_slash_suggestion should match snapshot 1`]
]
}
testID="app_slash_suggestion.list"
updateCellsBatchingPeriod={50}
windowSize={21}
/>
`;

View File

@@ -103,7 +103,7 @@ describe('components/autocomplete/app_slash_suggestion', () => {
expect(wrapper.state('dataSource')).toEqual([]);
});
test('should show commands from app sub commands', async (done) => {
test('should show commands from app sub commands', (done?: jest.DoneCallback) => {
const props: Props = {
...baseProps,
};
@@ -126,7 +126,9 @@ describe('components/autocomplete/app_slash_suggestion', () => {
setTimeout(() => {
expect(wrapper.state('dataSource')).toEqual(expected);
done();
if (done) {
done();
}
});
});

View File

@@ -60,9 +60,7 @@ exports[`ChannelLoader should match snapshot 1`] = `
}
>
<ActivityIndicator
animating={true}
color="#FFFFFF"
hidesWhenStopped={true}
size="small"
/>
</View>

View File

@@ -23,6 +23,8 @@ describe('ChannelLoader', () => {
test('should call setTimeout and setInterval for showIndicator and retryLoad on mount', () => {
shallow(<ChannelLoader {...baseProps}/>);
const setTimeout = jest.spyOn(global, 'setTimeout');
const setInterval = jest.spyOn(global, 'setInterval');
expect(setTimeout).not.toHaveBeenCalled();
expect(setInterval).not.toHaveBeenCalled();
@@ -42,6 +44,8 @@ describe('ChannelLoader', () => {
retryLoad: jest.fn(),
};
const wrapper = shallow(<ChannelLoader {...props}/>);
const clearTimeout = jest.spyOn(global, 'clearTimeout');
const clearInterval = jest.spyOn(global, 'clearInterval');
const instance = wrapper.instance();
instance.componentWillUnmount();

View File

@@ -20,15 +20,12 @@ exports[`CustomList should match snapshot with FlatList 1`] = `
},
]
}
disableVirtualization={false}
horizontal={false}
initialNumToRender={15}
keyExtractor={[Function]}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={16}
numColumns={1}
onEndReachedThreshold={2}
onLayout={[Function]}
onScroll={[Function]}
removeClippedSubviews={true}
@@ -41,8 +38,6 @@ exports[`CustomList should match snapshot with FlatList 1`] = `
"flex": 1,
}
}
updateCellsBatchingPeriod={50}
windowSize={21}
/>
`;
@@ -56,16 +51,12 @@ exports[`CustomList should match snapshot with SectionList 1`] = `
"flexGrow": 1,
}
}
data={Array []}
disableVirtualization={false}
extraData={false}
horizontal={false}
initialNumToRender={15}
keyExtractor={[Function]}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={16}
onEndReachedThreshold={2}
onLayout={[Function]}
onScroll={[Function]}
removeClippedSubviews={true}
@@ -89,8 +80,6 @@ exports[`CustomList should match snapshot with SectionList 1`] = `
"flex": 1,
}
}
updateCellsBatchingPeriod={50}
windowSize={21}
/>
`;

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/custom_status/clear_button should match snapshot 1`] = `
<ForwardRef
<TouchableOpacity
onPress={[Function]}
style={
Array [
@@ -27,5 +27,5 @@ exports[`components/custom_status/clear_button should match snapshot 1`] = `
}
}
/>
</ForwardRef>
</TouchableOpacity>
`;

View File

@@ -87,15 +87,10 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
>
<SectionList
ListFooterComponent={[Function]}
data={Array []}
disableVirtualization={false}
getItemLayout={[Function]}
horizontal={false}
initialNumToRender={50}
keyExtractor={[Function]}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={10}
nativeID="emojiPicker"
onEndReached={[Function]}
onEndReachedThreshold={0}
@@ -106,7 +101,6 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
removeClippedSubviews={true}
renderItem={[Function]}
renderSectionHeader={[Function]}
scrollEventThrottle={50}
sections={
Array [
Object {
@@ -12888,7 +12882,6 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
]
}
showsVerticalScrollIndicator={false}
stickySectionHeadersEnabled={true}
style={
Array [
Object {},
@@ -12897,8 +12890,6 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
},
]
}
updateCellsBatchingPeriod={50}
windowSize={21}
/>
<KeyboardTrackingView
normalList={true}
@@ -12926,7 +12917,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
}
}
>
<ForwardRef
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -12951,8 +12942,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
]
}
/>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -12975,8 +12966,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
]
}
/>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -12999,8 +12990,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
]
}
/>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -13023,8 +13014,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
]
}
/>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -13047,8 +13038,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
]
}
/>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -13071,8 +13062,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
]
}
/>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -13095,8 +13086,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
]
}
/>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -13119,8 +13110,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
]
}
/>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -13143,8 +13134,8 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
]
}
/>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -13167,7 +13158,7 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
]
}
/>
</ForwardRef>
</TouchableOpacity>
</View>
</View>
</KeyboardTrackingView>

View File

@@ -60,17 +60,17 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
];
testCases.forEach((testCase) => {
test(`'${testCase.input}' should return '${testCase.output}'`, async () => {
test(`'${testCase.input}' should return '${testCase.output}'`, () => {
expect(filterEmojiSearchInput(testCase.input)).toEqual(testCase.output);
});
});
test('should match snapshot', async () => {
test('should match snapshot', () => {
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('searchEmojis should return the right values on fuse', async () => {
test('searchEmojis should return the right values on fuse', () => {
const input = '1';
const output = ['100', '1234', '1st_place_medal', '+1', '-1', 'clock1', 'clock10', 'clock1030', 'clock11', 'clock1130', 'clock12', 'clock1230', 'clock130', 'u7121', 'u7981'];
@@ -79,7 +79,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
expect(result).toEqual(output);
});
test('should rebuild emojis emojis when emojis change', async () => {
test('should rebuild emojis emojis when emojis change', () => {
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
const instance = wrapper.instance();
const renderableEmojis = jest.spyOn(instance, 'renderableEmojis');
@@ -92,7 +92,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
expect(renderableEmojis).toHaveBeenCalledWith(baseProps.emojisBySection, baseProps.deviceWidth);
});
test('should set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is true', async () => {
test('should set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is true', () => {
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
const instance = wrapper.instance();
instance.setState = jest.fn();
@@ -107,7 +107,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
expect(instance.rebuildEmojis).toBe(false);
});
test('should not set rebuilt emojis when rebuildEmojis is false and searchBarAnimationComplete is true', async () => {
test('should not set rebuilt emojis when rebuildEmojis is false and searchBarAnimationComplete is true', () => {
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
const instance = wrapper.instance();
instance.setState = jest.fn();
@@ -120,7 +120,7 @@ describe('components/emoji_picker/emoji_picker.ios', () => {
expect(instance.setState).not.toHaveBeenCalled();
});
test('should not set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is false', async () => {
test('should not set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is false', () => {
const wrapper = shallowWithIntl(<EmojiPicker {...baseProps}/>);
const instance = wrapper.instance();
instance.setState = jest.fn();

View File

@@ -51,7 +51,7 @@ exports[`Global Thread Footer Should render for channel view and unfollow the th
>
2 replies
</Text>
<ForwardRef
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -75,7 +75,7 @@ exports[`Global Thread Footer Should render for channel view and unfollow the th
>
Following
</Text>
</ForwardRef>
</TouchableOpacity>
</View>
`;

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Global Thread Item Should render thread item with unread messages dot 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
testID="thread_item.post1.item"
underlayColor="rgba(28,88,217,0.08)"
@@ -180,11 +180,11 @@ exports[`Global Thread Item Should render thread item with unread messages dot 1
/>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;
exports[`Global Thread Item Should show unread mentions count 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
testID="thread_item.post1.item"
underlayColor="rgba(28,88,217,0.08)"
@@ -378,5 +378,5 @@ exports[`Global Thread Item Should show unread mentions count 1`] = `
/>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;

View File

@@ -130,24 +130,18 @@ exports[`Global Thread List Should render threads with functional tabs & mark al
"thread1",
]
}
disableVirtualization={false}
horizontal={false}
initialNumToRender={10}
keyExtractor={[Function]}
maxToRenderPerBatch={10}
numColumns={1}
onEndReached={[Function]}
onEndReachedThreshold={2}
removeClippedSubviews={true}
renderItem={[Function]}
scrollEventThrottle={50}
scrollIndicatorInsets={
Object {
"right": 1,
}
}
updateCellsBatchingPeriod={50}
windowSize={21}
/>
</View>
`;

View File

@@ -10,7 +10,7 @@ exports[`Global Thread List Header Should render threads with functional tabs &
}
>
<View>
<ForwardRef
<TouchableOpacity
onPress={[MockFunction]}
testID="thread_list.all_threads"
>
@@ -33,8 +33,8 @@ exports[`Global Thread List Header Should render threads with functional tabs &
All Your Threads
</Text>
</View>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[MockFunction]}
testID="thread_list.unread_threads"
>
@@ -62,10 +62,10 @@ exports[`Global Thread List Header Should render threads with functional tabs &
/>
</View>
</View>
</ForwardRef>
</TouchableOpacity>
</View>
<View>
<ForwardRef
<TouchableOpacity
disabled={false}
onPress={[MockFunction]}
testID="thread_list.mark_all_read"
@@ -78,7 +78,7 @@ exports[`Global Thread List Header Should render threads with functional tabs &
]
}
/>
</ForwardRef>
</TouchableOpacity>
</View>
</View>
`;

View File

@@ -4,7 +4,7 @@
import AsyncStorage from '@react-native-community/async-storage';
import {PureComponent} from 'react';
import {Dimensions} from 'react-native';
import {Dimensions, EmitterSubscription} from 'react-native';
import {DeviceTypes} from '@constants';
import mattermostManaged from '@mattermost-managed';
@@ -12,6 +12,7 @@ import EventEmitter from '@mm-redux/utils/event_emitter';
// TODO: Use permanentSidebar and splitView hooks instead
export default class ImageViewPort extends PureComponent {
dimensionsListener: EmitterSubscription | undefined;
mounted = false;
state = {
isSplitView: false,
@@ -23,13 +24,13 @@ export default class ImageViewPort extends PureComponent {
this.handlePermanentSidebar();
this.handleDimensions();
EventEmitter.on(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS, this.handlePermanentSidebar);
Dimensions.addEventListener('change', this.handleDimensions);
this.dimensionsListener = Dimensions.addEventListener('change', this.handleDimensions);
}
componentWillUnmount() {
this.mounted = false;
EventEmitter.off(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS, this.handlePermanentSidebar);
Dimensions.removeEventListener('change', this.handleDimensions);
this.dimensionsListener?.remove();
}
handleDimensions = () => {

View File

@@ -78,7 +78,6 @@ export default class MarkdownCodeBlock extends React.PureComponent {
const actionText = formatMessage({id: 'mobile.markdown.code.copy_code', defaultMessage: 'Copy Code'});
BottomSheet.showBottomSheetWithOptions({
options: [actionText, cancelText],
cancelButtonIndex: 1,
}, (value) => {
if (value !== 1) {
this.handleCopyCode();

View File

@@ -151,7 +151,6 @@ export default class MarkdownImage extends ImageViewPort {
const actionText = formatMessage({id: 'mobile.markdown.link.copy_url', defaultMessage: 'Copy URL'});
BottomSheet.showBottomSheetWithOptions({
options: [actionText, cancelText],
cancelButtonIndex: 1,
}, (value) => {
if (value !== 1) {
this.handleLinkCopy();

View File

@@ -130,7 +130,6 @@ export default class MarkdownLink extends PureComponent {
const actionText = formatMessage({id: 'mobile.markdown.link.copy_url', defaultMessage: 'Copy URL'});
BottomSheet.showBottomSheetWithOptions({
options: [actionText, cancelText],
cancelButtonIndex: 1,
}, (value) => {
if (value !== 1) {
this.handleLinkCopy();

View File

@@ -45,14 +45,14 @@ export default class MarkdownTable extends React.PureComponent {
}
componentDidMount() {
Dimensions.addEventListener('change', this.setMaxPreviewColumns);
this.dimensionsListener = Dimensions.addEventListener('change', this.setMaxPreviewColumns);
const window = Dimensions.get('window');
this.setMaxPreviewColumns({window});
}
componentWillUnmount() {
Dimensions.removeEventListener('change', this.setMaxPreviewColumns);
this.dimensionsListener?.remove();
}
setMaxPreviewColumns = ({window}) => {

View File

@@ -181,10 +181,10 @@ const NetworkIndicator = ({
}
});
AppState.addEventListener('change', handleAppStateChange);
const listener = AppState.addEventListener('change', handleAppStateChange);
return () => {
AppState.removeEventListener('change', handleAppStateChange);
listener.remove();
if (clearNotificationTimeout.current && AppState.currentState !== 'active') {
clearTimeout(clearNotificationTimeout.current);
}

View File

@@ -1,13 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PasteableTextInput should render pasteable text input 1`] = `
<TextInput
allowFontScaling={true}
onPaste={[Function]}
rejectResponderTermination={true}
screenId="Channel"
underlineColorAndroid="transparent"
>
My Text
</TextInput>
`;

View File

@@ -1,77 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import PropTypes from 'prop-types';
import React from 'react';
import {NativeEventEmitter, NativeModules, Platform, TextInput} from 'react-native';
import {PASTE_FILES} from '@constants/post_draft';
import EventEmitter from '@mm-redux/utils/event_emitter';
const {OnPasteEventManager} = NativeModules;
const OnPasteEventEmitter = new NativeEventEmitter(OnPasteEventManager);
export class PasteableTextInput extends React.PureComponent {
static propTypes = {
...TextInput.PropTypes,
forwardRef: PropTypes.any,
screenId: PropTypes.string.isRequired,
}
componentDidMount() {
this.subscription = OnPasteEventEmitter.addListener('onPaste', this.onPaste);
}
componentWillUnmount() {
if (this.subscription) {
this.subscription.remove();
}
}
getLastSubscriptionKey = () => {
const subscriptions = OnPasteEventEmitter._subscriber._subscriptionsForType.onPaste?.filter((sub) => sub); // eslint-disable-line no-underscore-dangle
return subscriptions?.length && subscriptions[subscriptions.length - 1].key;
}
onPaste = (event) => {
const lastSubscriptionKey = this.getLastSubscriptionKey();
if (this.subscription.key !== lastSubscriptionKey) {
return;
}
let data = null;
let error = null;
if (Platform.OS === 'android') {
const {nativeEvent} = event;
data = nativeEvent.data;
error = nativeEvent.error;
} else {
data = event;
}
EventEmitter.emit(PASTE_FILES, error, data, this.props.screenId);
}
render() {
const {testID, forwardRef, ...props} = this.props;
return (
<TextInput
testID={testID}
{...props}
onPaste={this.onPaste}
ref={forwardRef}
/>
);
}
}
const WrappedPasteableTextInput = (props, ref) => (
<PasteableTextInput
{...props}
forwardRef={ref}
/>
);
export default React.forwardRef(WrappedPasteableTextInput);

View File

@@ -1,62 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {NativeEventEmitter} from 'react-native';
import {PASTE_FILES} from '@constants/post_draft';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {PasteableTextInput} from './index';
const nativeEventEmitter = new NativeEventEmitter();
describe('PasteableTextInput', () => {
const emit = jest.spyOn(EventEmitter, 'emit');
test('should render pasteable text input', () => {
const onPaste = jest.fn();
const text = 'My Text';
const component = shallow(
<PasteableTextInput
onPaste={onPaste}
screenId='Channel'
>{text}</PasteableTextInput>,
);
expect(component).toMatchSnapshot();
});
test('should call onPaste props if native onPaste trigger', () => {
const event = {someData: 'data'};
const text = 'My Text';
shallow(
<PasteableTextInput screenId='Channel'>{text}</PasteableTextInput>,
);
nativeEventEmitter.emit('onPaste', event);
expect(emit).toHaveBeenCalledWith(PASTE_FILES, null, event, 'Channel');
});
test('should remove onPaste listener when unmount', () => {
const mockRemove = jest.fn();
const text = 'My Text';
const component = shallow(
<PasteableTextInput screenId='Channel'>{text}</PasteableTextInput>,
);
component.instance().subscription.remove = mockRemove;
component.instance().componentWillUnmount();
expect(mockRemove).toHaveBeenCalled();
});
test('should emit PASTE_FILES event only for last subscription', () => {
const component1 = shallow(<PasteableTextInput screenId='Channel'/>);
const instance1 = component1.instance();
const component2 = shallow(<PasteableTextInput screenId='Thread'/>);
const instance2 = component2.instance();
instance1.onPaste();
expect(emit).not.toHaveBeenCalled();
instance2.onPaste();
expect(emit).toHaveBeenCalledTimes(1);
});
});

View File

@@ -68,7 +68,9 @@ exports[`PostDraft Should render the Archived for channelIsArchived 1`] = `
<View
accessibilityRole="button"
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
@@ -172,7 +174,9 @@ exports[`PostDraft Should render the Archived for deactivatedChannel 1`] = `
<View
accessibilityRole="button"
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
@@ -213,6 +217,8 @@ exports[`PostDraft Should render the DraftInput 1`] = `
inverted={false}
>
<View
collapsable={false}
nativeID="animatedComponent"
style={
Object {
"bottom": 0,
@@ -278,33 +284,49 @@ exports[`PostDraft Should render the DraftInput 1`] = `
}
>
<View>
<TextInput
allowFontScaling={true}
<PasteInput
accessible={true}
autoCapitalize="sentences"
autoCompleteType="off"
blurOnSubmit={false}
disableCopyPaste={false}
disableFullscreenUI={true}
focusable={true}
keyboardAppearance="light"
keyboardType="default"
mostRecentEventCount={0}
multiline={true}
onBlur={[Function]}
onChange={[Function]}
onChangeText={[Function]}
onClick={[Function]}
onEndEditing={[Function]}
onFocus={[Function]}
onPaste={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onScroll={[Function]}
onSelectionChange={[Function]}
onStartShouldSetResponder={[Function]}
placeholder="Write to "
placeholderTextColor="rgba(63,67,80,0.5)"
rejectResponderTermination={true}
screenId="NavigationScreen1"
style={
Object {
"color": "#3f4350",
"fontSize": 15,
"lineHeight": 20,
"maxHeight": 150,
"minHeight": 30,
"paddingBottom": 6,
"paddingHorizontal": 12,
"paddingTop": 6,
}
Array [
Object {
"color": "#3f4350",
"fontSize": 15,
"lineHeight": 20,
"maxHeight": 150,
"minHeight": 30,
"paddingBottom": 6,
"paddingHorizontal": 12,
"paddingTop": 6,
},
]
}
testID="post_draft.post.input"
textContentType="none"
@@ -319,8 +341,10 @@ exports[`PostDraft Should render the DraftInput 1`] = `
}
>
<View
collapsable={false}
forwardedRef={[Function]}
isInteraction={true}
nativeID="animatedComponent"
style={
Object {
"display": "flex",
@@ -349,8 +373,10 @@ exports[`PostDraft Should render the DraftInput 1`] = `
</RCTScrollView>
</View>
<View
collapsable={false}
forwardedRef={[Function]}
isInteraction={true}
nativeID="animatedComponent"
style={
Object {
"height": 0,
@@ -391,7 +417,9 @@ exports[`PostDraft Should render the DraftInput 1`] = `
>
<View
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
@@ -417,7 +445,9 @@ exports[`PostDraft Should render the DraftInput 1`] = `
</View>
<View
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
@@ -443,7 +473,9 @@ exports[`PostDraft Should render the DraftInput 1`] = `
</View>
<View
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
@@ -469,7 +501,9 @@ exports[`PostDraft Should render the DraftInput 1`] = `
</View>
<View
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
@@ -495,7 +529,9 @@ exports[`PostDraft Should render the DraftInput 1`] = `
</View>
<View
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}

View File

@@ -1,15 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PostInput should match, full snapshot 1`] = `
<ForwardRef(WrappedPasteableTextInput)
<ForwardRef
autoCompleteType="off"
blurOnSubmit={false}
disableCopyPaste={false}
disableFullscreenUI={true}
keyboardAppearance="light"
keyboardType="default"
multiline={true}
onChangeText={[Function]}
onEndEditing={[Function]}
onPaste={[Function]}
onSelectionChange={[Function]}
placeholder="Write to Test Channel"
placeholderTextColor="rgba(63,67,80,0.5)"

View File

@@ -6,10 +6,11 @@ import React, {PureComponent} from 'react';
import {intlShape} from 'react-intl';
import {Alert, AppState, findNodeHandle, Keyboard, NativeModules, Platform} from 'react-native';
import PasteableTextInput from '@components/pasteable_text_input';
import {NavigationTypes} from '@constants';
import DEVICE from '@constants/device';
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT} from '@constants/post_draft';
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT, PASTE_FILES} from '@constants/post_draft';
import mattermostManaged from '@mattermost-managed';
import PasteableTextInput from '@mattermost/react-native-paste-input';
import {debounce} from '@mm-redux/actions/helpers';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {t} from '@utils/i18n';
@@ -55,6 +56,7 @@ export default class PostInput extends PureComponent {
this.state = {
keyboardType: 'default',
longMessageAlertShown: false,
disableCopyAndPaste: mattermostManaged.getCachedConfig()?.copyAndPasteProtection === 'true',
};
}
@@ -62,10 +64,11 @@ export default class PostInput extends PureComponent {
const event = this.props.rootId ? INSERT_TO_COMMENT : INSERT_TO_DRAFT;
EventEmitter.on(event, this.handleInsertTextToDraft);
EventEmitter.on(NavigationTypes.BLUR_POST_DRAFT, this.blur);
AppState.addEventListener('change', this.handleAppStateChange);
this.appStateListener = AppState.addEventListener('change', this.handleAppStateChange);
this.managedListener = mattermostManaged.addEventListener('managedConfigDidChange', this.onManagedConfigurationChange);
if (Platform.OS === 'android') {
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
this.keyboardListener = Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
@@ -73,10 +76,11 @@ export default class PostInput extends PureComponent {
const event = this.props.rootId ? INSERT_TO_COMMENT : INSERT_TO_DRAFT;
EventEmitter.off(NavigationTypes.BLUR_POST_DRAFT, this.blur);
EventEmitter.off(event, this.handleInsertTextToDraft);
AppState.removeEventListener('change', this.handleAppStateChange);
mattermostManaged.removeEventListener(this.managedListener);
this.appStateListener.remove();
if (Platform.OS === 'android') {
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
this.keyboardListener?.remove();
}
this.changeDraft(this.getValue());
@@ -249,11 +253,20 @@ export default class PostInput extends PureComponent {
}
this.value = completed;
this.input.current.setNativeProps({
text: completed,
});
};
onManagedConfigurationChange = (config) => {
this.setState({disableCopyAndPaste: config.copyAndPasteProtection === 'true'});
}
onPaste = (error, files) => {
EventEmitter.emit(PASTE_FILES, error, files, this.props.screenId);
}
resetTextInput = () => {
if (this.input.current) {
this.input.current.setNativeProps({
@@ -287,6 +300,7 @@ export default class PostInput extends PureComponent {
<PasteableTextInput
testID={testID}
ref={this.input}
disableCopyPaste={this.state.disableCopyAndPaste}
style={{...style.input, maxHeight}}
onChangeText={this.handleTextChange}
onSelectionChange={this.handlePostDraftSelectionChanged}
@@ -297,6 +311,7 @@ export default class PostInput extends PureComponent {
underlineColorAndroid='transparent'
keyboardType={this.state.keyboardType}
onEndEditing={this.handleEndEditing}
onPaste={this.onPaste}
disableFullscreenUI={true}
textContentType='none'
autoCompleteType='off'

View File

@@ -3,7 +3,9 @@
exports[`CameraButton should match snapshot 1`] = `
<View
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}

View File

@@ -3,7 +3,9 @@
exports[`FileQuickAction should match snapshot 1`] = `
<View
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}

View File

@@ -3,7 +3,9 @@
exports[`ImageQuickAction should match snapshot 1`] = `
<View
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}

View File

@@ -18,7 +18,7 @@ exports[`UploadItem downloadAndUploadFile should match, full snapshot 1`] = `
}
}
>
<ForwardRef
<TouchableOpacity
onPress={[Function]}
>
<View
@@ -68,7 +68,7 @@ exports[`UploadItem downloadAndUploadFile should match, full snapshot 1`] = `
}
/>
</View>
</ForwardRef>
</TouchableOpacity>
</View>
<UploadRemove
channelId="channel-id"

View File

@@ -17,6 +17,7 @@ describe('Uploads', () => {
handleRemoveLastFile: jest.fn(),
initUploadFiles: jest.fn(),
maxFileSize: 100,
maxFileCount: 10,
screenId: 'Channel',
theme: Preferences.THEMES.denim,
};

View File

@@ -55,7 +55,6 @@ describe('MoreMessagesButton', () => {
describe('lifecycle methods', () => {
AppState.addEventListener = jest.fn();
AppState.removeEventListener = jest.fn();
test('componentDidMount should register app state listener, indicator visibility listener, viewable items listener, and scroll end index listener', () => {
EventEmitter.on = jest.fn();
@@ -94,7 +93,6 @@ describe('MoreMessagesButton', () => {
instance.removeScrollEndIndexListener = jest.fn();
instance.componentWillUnmount();
expect(AppState.removeEventListener).toHaveBeenCalledWith('change', instance.onAppStateChange);
expect(EventEmitter.off).toHaveBeenCalledWith(ViewTypes.INDICATOR_BAR_VISIBLE, instance.onIndicatorBarVisible);
expect(instance.removeViewableItemsListener).toHaveBeenCalled();
expect(instance.removeScrollEndIndexListener).toHaveBeenCalled();
@@ -323,6 +321,8 @@ describe('MoreMessagesButton', () => {
<MoreMessagesButton {...baseProps}/>,
);
const instance = wrapper.instance();
const clearTimeout = jest.spyOn(global, 'clearTimeout');
wrapper.setProps({unreadCount: 10});
instance.setState({moreText: '60+ new messages'});
const autoCancelTimer = jest.fn();
@@ -517,6 +517,8 @@ describe('MoreMessagesButton', () => {
<MoreMessagesButton {...baseProps}/>,
);
const instance = wrapper.instance();
const setTimeout = jest.spyOn(global, 'setTimeout');
instance.componentDidUpdate = jest.fn();
instance.canceled = false;
instance.hide = jest.fn();
@@ -539,6 +541,8 @@ describe('MoreMessagesButton', () => {
<MoreMessagesButton {...baseProps}/>,
);
const instance = wrapper.instance();
const setTimeout = jest.spyOn(global, 'setTimeout');
instance.componentDidUpdate = jest.fn();
instance.canceled = false;
instance.hide = jest.fn();
@@ -564,7 +568,9 @@ describe('MoreMessagesButton', () => {
<MoreMessagesButton {...baseProps}/>,
);
const instance = wrapper.instance();
const clearTimeout = jest.spyOn(global, 'clearTimeout');
const autoCancelTimer = jest.fn();
instance.autoCancelTimer = autoCancelTimer;
instance.canceled = true;
instance.disableViewableItems = true;
@@ -621,6 +627,7 @@ describe('MoreMessagesButton', () => {
it('should return early when newMessageLineIndex <= 0', () => {
const viewableItems = [{index: 0}, {index: 1}];
const clearTimeout = jest.spyOn(global, 'clearTimeout');
wrapper.setProps({newMessageLineIndex: 0});
instance.onViewableItemsChanged(viewableItems);
@@ -633,8 +640,9 @@ describe('MoreMessagesButton', () => {
it('should return early when viewableItems length is 0', () => {
const viewableItems = [];
wrapper.setProps({newMessageLineIndex: 1, unreadCount: 10});
const clearTimeout = jest.spyOn(global, 'clearTimeout');
wrapper.setProps({newMessageLineIndex: 1, unreadCount: 10});
instance.onViewableItemsChanged(viewableItems);
expect(clearTimeout).not.toHaveBeenCalled();
});
@@ -643,6 +651,7 @@ describe('MoreMessagesButton', () => {
const viewableItems = [{index: 0}];
wrapper.setProps({newMessageLineIndex: 1, unreadCount: 10});
instance.disableViewableItems = true;
const clearTimeout = jest.spyOn(global, 'clearTimeout');
instance.onViewableItemsChanged(viewableItems);
expect(clearTimeout).not.toHaveBeenCalled();
@@ -685,6 +694,7 @@ describe('MoreMessagesButton', () => {
instance.disableViewableItems = false;
instance.autoCancelTimer = null;
instance.cancel = jest.fn();
const setTimeout = jest.spyOn(global, 'setTimeout');
instance.onViewableItemsChanged(viewableItems);
jest.runOnlyPendingTimers();
@@ -701,6 +711,7 @@ describe('MoreMessagesButton', () => {
instance.disableViewableItems = false;
instance.autoCancelTimer = null;
instance.cancel = jest.fn();
const setTimeout = jest.spyOn(global, 'setTimeout');
instance.onViewableItemsChanged(viewableItems);
jest.runOnlyPendingTimers();

View File

@@ -3,7 +3,7 @@
import React from 'react';
import {intlShape} from 'react-intl';
import {ActivityIndicator, Animated, AppState, AppStateStatus, Text, View, ViewToken} from 'react-native';
import {ActivityIndicator, Animated, AppState, AppStateStatus, NativeEventSubscription, Text, View, ViewToken} from 'react-native';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
@@ -64,6 +64,7 @@ export default class MoreMessageButton extends React.PureComponent<MoreMessagesB
intl: intlShape.isRequired,
};
appStateListener: NativeEventSubscription | undefined;
autoCancelTimer: undefined | null | NodeJS.Timeout;
buttonVisible = false;
canceled = false;
@@ -78,14 +79,14 @@ export default class MoreMessageButton extends React.PureComponent<MoreMessagesB
viewableItems: ViewToken[] = [];
componentDidMount() {
AppState.addEventListener('change', this.onAppStateChange);
this.appStateListener = AppState.addEventListener('change', this.onAppStateChange);
EventEmitter.on(ViewTypes.INDICATOR_BAR_VISIBLE, this.onIndicatorBarVisible);
this.removeViewableItemsListener = this.props.registerViewableItemsListener(this.onViewableItemsChanged);
this.removeScrollEndIndexListener = this.props.registerScrollEndIndexListener(this.onScrollEndIndex);
}
componentWillUnmount() {
AppState.removeEventListener('change', this.onAppStateChange);
this.appStateListener?.remove();
EventEmitter.off(ViewTypes.INDICATOR_BAR_VISIBLE, this.onIndicatorBarVisible);
if (this.removeViewableItemsListener) {
this.removeViewableItemsListener();

View File

@@ -178,6 +178,8 @@ const Body = ({
{body}
{post.failed &&
<Failed
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
post={post}
theme={theme}
/>

View File

@@ -2,20 +2,21 @@
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {StyleSheet} from 'react-native';
import {intlShape, injectIntl} from 'react-intl';
import {Keyboard, StyleSheet} from 'react-native';
import {showModalOverCurrentContext} from '@actions/navigation';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import NavigationTypes from '@constants/navigation';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {t} from '@utils/i18n';
import type {Post} from '@mm-redux/types/posts';
import type {Theme} from '@mm-redux/types/theme';
type FailedProps = {
createPost: (post: Post) => void;
intl: typeof intlShape;
post: Post;
removePost: (post: Post) => void;
theme: Theme;
@@ -28,38 +29,39 @@ const styles = StyleSheet.create({
},
});
const Failed = ({createPost, post, removePost, theme}: FailedProps) => {
const Failed = ({createPost, intl, post, removePost, theme}: FailedProps) => {
const onPress = useCallback(() => {
const screen = 'OptionsModal';
const passProps = {
title: {
id: t('mobile.post.failed_title'),
title: intl.formatMessage({
id: 'mobile.post.failed_title',
defaultMessage: 'Unable to send your message:',
},
}),
items: [{
action: () => {
EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL);
createPost(post);
},
text: {
id: t('mobile.post.failed_retry'),
text: intl.formatMessage({
id: 'mobile.post.failed_retry',
defaultMessage: 'Try Again',
},
}),
}, {
action: () => {
EventEmitter.emit(NavigationTypes.NAVIGATION_CLOSE_MODAL);
removePost(post);
},
text: {
id: t('mobile.post.failed_delete'),
text: intl.formatMessage({
id: 'mobile.post.failed_delete',
defaultMessage: 'Delete Message',
},
}),
textStyle: {
color: '#CC3239',
},
}],
};
Keyboard.dismiss();
showModalOverCurrentContext(screen, passProps);
}, []);
@@ -77,5 +79,4 @@ const Failed = ({createPost, post, removePost, theme}: FailedProps) => {
</TouchableWithFeedback>
);
};
export default Failed;
export default injectIntl(Failed);

View File

@@ -82,14 +82,14 @@ export default class DrawerLayout extends Component {
}
componentDidMount() {
Dimensions.addEventListener('change', this.handleDimensionsChange);
this.dimensionsListener = Dimensions.addEventListener('change', this.handleDimensionsChange);
}
componentWillUnmount() {
if (this.openValue) {
this.openValue.removeListener(this.handleOpenValueChanged);
}
Dimensions.removeEventListener('change', this.handleDimensionsChange);
this.dimensionsListener?.remove();
}
handleDimensionsChange = ({window}) => {

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ChannelItem should match snapshot 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -108,11 +108,11 @@ exports[`ChannelItem should match snapshot 1`] = `
</Text>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;
exports[`ChannelItem should match snapshot for current user i.e currentUser (you) 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -228,11 +228,11 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
</Text>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;
exports[`ChannelItem should match snapshot for current user i.e currentUser (you) when isSearchResult 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -348,11 +348,11 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
</Text>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;
exports[`ChannelItem should match snapshot for deactivated user and is currentChannel 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -468,11 +468,11 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
</Text>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;
exports[`ChannelItem should match snapshot for deactivated user and is searchResult 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -579,11 +579,11 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
</Text>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;
exports[`ChannelItem should match snapshot for deactivated user and not searchResults or currentChannel 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -690,11 +690,11 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
</Text>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;
exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -801,7 +801,7 @@ exports[`ChannelItem should match snapshot for isManualUnread 1`] = `
</Text>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;
exports[`ChannelItem should match snapshot for no displayName 1`] = `null`;
@@ -809,7 +809,7 @@ exports[`ChannelItem should match snapshot for no displayName 1`] = `null`;
exports[`ChannelItem should match snapshot for showUnreadForMsgs 1`] = `null`;
exports[`ChannelItem should match snapshot with custom status emoji 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -936,11 +936,11 @@ exports[`ChannelItem should match snapshot with custom status emoji 1`] = `
/>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;
exports[`ChannelItem should match snapshot with draft 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -1047,11 +1047,11 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
</Text>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;
exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -1194,5 +1194,5 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
/>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;

View File

@@ -10,32 +10,23 @@ exports[`ChannelsList List should match snapshot 1`] = `
"paddingBottom": 44,
}
}
data={Array []}
disableVirtualization={false}
horizontal={false}
initialNumToRender={10}
keyExtractor={[Function]}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={10}
onEndReachedThreshold={2}
onScrollBeginDrag={[Function]}
onViewableItemsChanged={[Function]}
removeClippedSubviews={false}
renderItem={[Function]}
renderSectionHeader={[Function]}
scrollEventThrottle={50}
sections={Array []}
stickySectionHeadersEnabled={true}
testID="main.sidebar.channels_list.list"
updateCellsBatchingPeriod={50}
viewabilityConfig={
Object {
"itemVisiblePercentThreshold": 100,
"waitForInteraction": true,
}
}
windowSize={21}
/>
</View>
`;
@@ -51,32 +42,23 @@ exports[`ChannelsList List should match snapshot with collapsed threads enabled
"paddingBottom": 44,
}
}
data={Array []}
disableVirtualization={false}
horizontal={false}
initialNumToRender={10}
keyExtractor={[Function]}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={10}
onEndReachedThreshold={2}
onScrollBeginDrag={[Function]}
onViewableItemsChanged={[Function]}
removeClippedSubviews={false}
renderItem={[Function]}
renderSectionHeader={[Function]}
scrollEventThrottle={50}
sections={Array []}
stickySectionHeadersEnabled={true}
testID="main.sidebar.channels_list.list"
updateCellsBatchingPeriod={50}
viewabilityConfig={
Object {
"itemVisiblePercentThreshold": 100,
"waitForInteraction": true,
}
}
windowSize={21}
/>
<UnreadIndicatorIOS
onPress={[Function]}
@@ -132,32 +114,23 @@ exports[`ChannelsList List should match snapshot with unreads not on top 1`] = `
"paddingBottom": 44,
}
}
data={Array []}
disableVirtualization={false}
horizontal={false}
initialNumToRender={10}
keyExtractor={[Function]}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={10}
onEndReachedThreshold={2}
onScrollBeginDrag={[Function]}
onViewableItemsChanged={[Function]}
removeClippedSubviews={false}
renderItem={[Function]}
renderSectionHeader={[Function]}
scrollEventThrottle={50}
sections={Array []}
stickySectionHeadersEnabled={true}
testID="main.sidebar.channels_list.list"
updateCellsBatchingPeriod={50}
viewabilityConfig={
Object {
"itemVisiblePercentThreshold": 100,
"waitForInteraction": true,
}
}
windowSize={21}
/>
<UnreadIndicatorIOS
onPress={[Function]}

View File

@@ -113,10 +113,9 @@ export default class List extends PureComponent {
this.props.orderedChannelIds !== orderedChannelIds) {
this.setSections(this.buildSections(this.props));
}
} else if (
} else if ( // Rebuild sections only if categories or unreads have changed
!isEqual(this.props.categories, categories) ||
this.props.unreadChannelIds !== unreadChannelIds) {
// Rebuild sections only if categories or unreads have changed
!isEqual(this.props.unreadChannelIds, unreadChannelIds)) {
this.setCategorySections(this.buildCategorySections());
}
@@ -213,7 +212,7 @@ export default class List extends PureComponent {
const moreChannelsText = formatMessage({id: 'more_channels.title', defaultMessage: 'Browse for a Channel'});
const newChannelText = formatMessage({id: 'mobile.create_channel', defaultMessage: 'Create a new Channel'});
const newDirectChannelText = formatMessage({id: 'mobile.more_dms.title', defaultMessage: 'Add a Conversation'});
const cancelText = formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'});
const options = [];
const actions = [];
@@ -229,20 +228,14 @@ export default class List extends PureComponent {
actions.push(this.goToDirectMessages);
options.push({text: newDirectChannelText, icon: 'account-plus-outline'});
options.push(cancelText);
const cancelButtonIndex = options.length - 1;
BottomSheet.showBottomSheetWithOptions({
anchor: this.combinedActionsRef?.current ? findNodeHandle(this.combinedActionsRef.current) : null,
options,
title: 'Add Channels',
subtitle: `To the ${category.display_name} category`,
cancelButtonIndex,
}, (value) => {
if (value !== cancelButtonIndex) {
actions[value]();
}
actions[value]();
});
};
@@ -472,6 +465,9 @@ export default class List extends PureComponent {
// Add the rest
if (this.props.categories) {
this.props.categories.reduce((prev, cat) => {
if (cat.type === 'favorites' && !cat.channel_ids.length) {
return prev;
}
prev.push({
name: cat.display_name,
action: cat.type === 'direct_messages' ? this.goToDirectMessages : () => this.showCreateChannelOptions(cat),
@@ -557,7 +553,6 @@ export default class List extends PureComponent {
ref={this.setListRef}
sections={showLegacySidebar ? sections : categorySections}
contentContainerStyle={{paddingBottom}}
removeClippedSubviews={Platform.OS === 'android'}
renderItem={showLegacySidebar ? this.renderItem : this.renderCategoryItem}
renderSectionHeader={showLegacySidebar ? this.renderSectionHeader : this.renderCategoryHeader}
keyboardShouldPersistTaps={'always'}

View File

@@ -4,7 +4,7 @@ exports[`SwitchTeamsButton should match snapshot 1`] = `
<View
testID="main.sidebar.channels_list.switch_teams.button"
>
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(25,42,77,0.5)"
>
@@ -59,7 +59,7 @@ exports[`SwitchTeamsButton should match snapshot 1`] = `
testID="main.sidebar.channels_list.switch_teams.button.team_icon"
/>
</View>
</ForwardRef>
</TouchableHighlight>
<Badge
containerStyle={
Object {

View File

@@ -56,7 +56,7 @@ export default class MainSidebarBase extends Component {
this.props.actions.getTeams();
EventEmitter.on(NavigationTypes.CLOSE_MAIN_SIDEBAR, this.closeMainSidebar);
EventEmitter.on(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
Dimensions.addEventListener('change', this.handleDimensions);
this.dimensionsListener = Dimensions.addEventListener('change', this.handleDimensions);
}
shouldComponentUpdate(nextProps, nextState) {
@@ -91,7 +91,7 @@ export default class MainSidebarBase extends Component {
this.mounted = false;
EventEmitter.off(NavigationTypes.CLOSE_MAIN_SIDEBAR, this.closeMainSidebar);
EventEmitter.off(WebsocketEvents.CHANNEL_UPDATED, this.handleUpdateTitle);
Dimensions.removeEventListener('change', this.handleDimensions);
this.dimensionsListener?.remove();
}
drawerSwiperRef = (ref) => {

View File

@@ -9,7 +9,7 @@ exports[`TeamsListItem should match snapshot 1`] = `
}
testID="main.sidebar.teams_list.flat_list.teams_list_item"
>
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -110,6 +110,6 @@ exports[`TeamsListItem should match snapshot 1`] = `
</Text>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
`;

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DrawerItem should match snapshot 1`] = `
<ForwardRef
<TouchableOpacity
onPress={[MockFunction]}
testID="test-id"
>
@@ -89,11 +89,11 @@ exports[`DrawerItem should match snapshot 1`] = `
/>
</View>
</View>
</ForwardRef>
</TouchableOpacity>
`;
exports[`DrawerItem should match snapshot without separator and centered false 1`] = `
<ForwardRef
<TouchableOpacity
onPress={[MockFunction]}
testID="test-id"
>
@@ -170,5 +170,5 @@ exports[`DrawerItem should match snapshot without separator and centered false 1
</View>
</View>
</View>
</ForwardRef>
</TouchableOpacity>
`;

View File

@@ -36,13 +36,13 @@ export default class SettingsDrawer extends SettingsSidebarBase {
super.componentDidMount();
this.handleDimensions({window: Dimensions.get('window')});
Dimensions.addEventListener('change', this.handleDimensions);
this.dimensionsListener = Dimensions.addEventListener('change', this.handleDimensions);
}
componentWillUnmount() {
super.componentWillUnmount();
Dimensions.removeEventListener('change', this.handleDimensions);
this.dimensionsListener?.remove();
}
confirmReset = (status) => {

View File

@@ -44,7 +44,7 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
}
}
>
<ForwardRef
<TouchableOpacity
onPress={[Function]}
>
<View
@@ -81,8 +81,8 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
}
}
/>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[Function]}
>
<View
@@ -119,8 +119,8 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
}
}
/>
</ForwardRef>
<ForwardRef
</TouchableOpacity>
<TouchableOpacity
onPress={[Function]}
>
<View
@@ -156,7 +156,7 @@ exports[`components/widgets/settings/RadioSetting should match snapshot when err
}
/>
</View>
</ForwardRef>
</TouchableOpacity>
</View>
<View>
<Text

View File

@@ -2,7 +2,6 @@
// See LICENSE.txt for license information.
/* eslint-disable max-lines */
import {isEqual} from 'lodash';
import {Client4} from '@client/rest';
import {getUser} from '@components/autocomplete/slash_suggestion/app_command_parser/app_command_parser_dependencies';
@@ -16,7 +15,7 @@ import {ActionFunc, batchActions, DispatchFunc, GetStateFunc} from '@mm-redux/ty
import {CategorySorting, ChannelCategory, OrderedChannelCategories} from '@mm-redux/types/channel_categories';
import {Channel} from '@mm-redux/types/channels';
import {UserProfile} from '@mm-redux/types/users';
import {$ID, IDMappedObjects, RelationOneToMany} from '@mm-redux/types/utilities';
import {$ID, RelationOneToMany} from '@mm-redux/types/utilities';
import {insertMultipleWithoutDuplicates, insertWithoutDuplicates, removeItem} from '@mm-redux/utils/array_utils';
import {getUserIdFromChannelName} from '@mm-redux/utils/channel_utils';
@@ -32,10 +31,23 @@ export function collapseCategory(categoryId: string) {
return setCategoryCollapsed(categoryId, true);
}
export function setCategoryCollapsed(categoryId: string, collapsed: boolean) {
return patchCategory(categoryId, {
collapsed,
});
export function setCategoryCollapsed(categoryId: string, collapsed: boolean): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
const state = getState();
const category = getCategory(state, categoryId);
const patchedCategory = {
...category,
collapsed,
};
dispatch({
type: ChannelCategoryTypes.RECEIVED_CATEGORY,
data: patchedCategory,
});
return {data: patchedCategory};
};
}
export function setCategorySorting(categoryId: string, sorting: CategorySorting) {
@@ -142,20 +154,11 @@ export function fetchMyCategories(teamId: string) {
return {error};
}
/*
* Make sure that we don't dispatch an unnecessary update after fetching
*/
const categoriesInState = getState().entities.channelCategories.byId;
const mappedCats = data.order.reduce((prev, categoryId) => {
return {
...prev,
[categoryId]: data.categories.find((category) => category.id === categoryId),
};
}, {} as IDMappedObjects<ChannelCategory>);
if (isEqual(mappedCats, categoriesInState)) {
return {data: false};
}
// Remove collapse state from server data
data.categories = data.categories.map((cat) => {
delete cat.collapsed;
return cat;
});
return dispatch(batchActions([
{
@@ -199,7 +202,7 @@ export function addChannelToInitialCategory(channel: Channel, setOnServer = fals
// Get the user ids in the channel
const allUsersInChannels: RelationOneToMany<Channel, UserProfile> = getUserIdsInChannels(state);
const allUsersInGMChannel = Array.from(allUsersInChannels[channel.id] || []);
const usersInGMChannel: Array<string> = allUsersInGMChannel.filter((u: string) => u !== currentUserId);
const usersInGMChannel: string[] = allUsersInGMChannel.filter((u: string) => u !== currentUserId);
// Filter and see if there are any missing in our state
const missingUsers = usersInGMChannel.filter((id) => {

View File

@@ -25,7 +25,7 @@ export type ChannelCategory = {
sorting: CategorySorting;
channel_ids: Array<$ID<Channel>>;
muted: boolean;
collapsed: boolean;
collapsed?: boolean;
};
export type OrderedChannelCategories = {

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SettingsSidebarDrawerButton should match, full snapshot 1`] = `
<ForwardRef
<TouchableOpacity
accessibilityHint="Opens the more options right hand sidebar"
accessibilityLabel="More Options"
accessibilityRole="button"
@@ -31,5 +31,5 @@ exports[`SettingsSidebarDrawerButton should match, full snapshot 1`] = `
size={25}
/>
</View>
</ForwardRef>
</TouchableOpacity>
`;

View File

@@ -111,11 +111,11 @@ const ChannelNavBar = (props) => {
useEffect(() => {
handleDimensions();
handlePermanentSidebar();
Dimensions.addEventListener('change', handleDimensions);
const dimensionsListener = Dimensions.addEventListener('change', handleDimensions);
EventEmitter.on(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS, handlePermanentSidebar);
return () => {
Dimensions.removeEventListener('change', handleDimensions);
dimensionsListener.remove();
EventEmitter.off(DeviceTypes.PERMANENT_SIDEBAR_SETTINGS, handlePermanentSidebar);
};
}, []);

View File

@@ -8,7 +8,7 @@ exports[`ChannelSearchButton should match, full snapshot 1`] = `
}
}
>
<ForwardRef
<TouchableOpacity
accessibilityHint="Opens the channel search modal"
accessibilityLabel="Search"
accessibilityRole="button"
@@ -43,6 +43,6 @@ exports[`ChannelSearchButton should match, full snapshot 1`] = `
}
/>
</View>
</ForwardRef>
</TouchableOpacity>
</View>
`;

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ChannelTitle should match snapshot 1`] = `
<ForwardRef
<TouchableOpacity
style={
Object {
"flex": 1,
@@ -54,11 +54,11 @@ exports[`ChannelTitle should match snapshot 1`] = `
}
/>
</View>
</ForwardRef>
</TouchableOpacity>
`;
exports[`ChannelTitle should match snapshot when is DM and has guests and the teammate is the guest (when can show subtitles) 1`] = `
<ForwardRef
<TouchableOpacity
style={
Object {
"flex": 1,
@@ -150,11 +150,11 @@ exports[`ChannelTitle should match snapshot when is DM and has guests and the te
}
/>
</View>
</ForwardRef>
</TouchableOpacity>
`;
exports[`ChannelTitle should match snapshot when is DM and has guests but the teammate is not the guest (when can show subtitles) 1`] = `
<ForwardRef
<TouchableOpacity
style={
Object {
"flex": 1,
@@ -221,11 +221,11 @@ exports[`ChannelTitle should match snapshot when is DM and has guests but the te
}
/>
</View>
</ForwardRef>
</TouchableOpacity>
`;
exports[`ChannelTitle should match snapshot when isChannelShared is true 1`] = `
<ForwardRef
<TouchableOpacity
style={
Object {
"flex": 1,
@@ -297,11 +297,11 @@ exports[`ChannelTitle should match snapshot when isChannelShared is true 1`] = `
}
/>
</View>
</ForwardRef>
</TouchableOpacity>
`;
exports[`ChannelTitle should match snapshot when isSelfDMChannel is true 1`] = `
<ForwardRef
<TouchableOpacity
style={
Object {
"flex": 1,
@@ -362,11 +362,11 @@ exports[`ChannelTitle should match snapshot when isSelfDMChannel is true 1`] = `
}
/>
</View>
</ForwardRef>
</TouchableOpacity>
`;
exports[`ChannelTitle should match snapshot with custom status emoji 1`] = `
<ForwardRef
<TouchableOpacity
style={
Object {
"flex": 1,
@@ -433,5 +433,5 @@ exports[`ChannelTitle should match snapshot with custom status emoji 1`] = `
}
/>
</View>
</ForwardRef>
</TouchableOpacity>
`;

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MainSidebarDrawerButton should match, full snapshot 1`] = `
<ForwardRef
<TouchableOpacity
accessibilityHint="Opens the channels and teams drawer"
accessibilityLabel="Channels and teams"
accessibilityRole="button"
@@ -37,11 +37,11 @@ exports[`MainSidebarDrawerButton should match, full snapshot 1`] = `
/>
</View>
</View>
</ForwardRef>
</TouchableOpacity>
`;
exports[`MainSidebarDrawerButton should match, full snapshot 2`] = `
<ForwardRef
<TouchableOpacity
accessibilityHint="Opens the channels and teams drawer"
accessibilityLabel="Channels and teams"
accessibilityRole="button"
@@ -107,5 +107,5 @@ exports[`MainSidebarDrawerButton should match, full snapshot 2`] = `
/>
</View>
</View>
</ForwardRef>
</TouchableOpacity>
`;

View File

@@ -90,7 +90,7 @@ exports[`channel_info_header should match snapshot 1`] = `
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -125,7 +125,7 @@ exports[`channel_info_header should match snapshot 1`] = `
Purpose string
</Text>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<View
style={
@@ -134,7 +134,7 @@ exports[`channel_info_header should match snapshot 1`] = `
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -271,7 +271,7 @@ exports[`channel_info_header should match snapshot 1`] = `
value="Header string"
/>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<Text
style={
@@ -424,7 +424,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -459,7 +459,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
Purpose string
</Text>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<View
style={
@@ -468,7 +468,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -605,7 +605,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests and is
value="Header string"
/>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<Text
style={
@@ -730,7 +730,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -765,7 +765,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
Purpose string
</Text>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<View
style={
@@ -774,7 +774,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -911,7 +911,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests but its
value="Header string"
/>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<Text
style={
@@ -1064,7 +1064,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -1099,7 +1099,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
Purpose string
</Text>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<View
style={
@@ -1108,7 +1108,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -1245,7 +1245,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
value="Header string"
/>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<Text
style={
@@ -1370,7 +1370,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -1405,7 +1405,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
Purpose string
</Text>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<View
style={
@@ -1414,7 +1414,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -1551,7 +1551,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
value="Header string"
/>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<Text
style={
@@ -1725,7 +1725,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -1760,7 +1760,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
Purpose string
</Text>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<View
style={
@@ -1769,7 +1769,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -1906,7 +1906,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
value="Header string"
/>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<Text
style={
@@ -2113,7 +2113,7 @@ exports[`channel_info_header should match snapshot with custom status enabled 1`
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -2148,7 +2148,7 @@ exports[`channel_info_header should match snapshot with custom status enabled 1`
Purpose string
</Text>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<View
style={
@@ -2157,7 +2157,7 @@ exports[`channel_info_header should match snapshot with custom status enabled 1`
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -2294,7 +2294,7 @@ exports[`channel_info_header should match snapshot with custom status enabled 1`
value="Header string"
/>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<Text
style={
@@ -2540,7 +2540,7 @@ exports[`channel_info_header should match snapshot with custom status expiry 1`]
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -2575,7 +2575,7 @@ exports[`channel_info_header should match snapshot with custom status expiry 1`]
Purpose string
</Text>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<View
style={
@@ -2584,7 +2584,7 @@ exports[`channel_info_header should match snapshot with custom status expiry 1`]
}
}
>
<ForwardRef
<TouchableHighlight
onLongPress={[Function]}
>
<View
@@ -2721,7 +2721,7 @@ exports[`channel_info_header should match snapshot with custom status expiry 1`]
value="Header string"
/>
</View>
</ForwardRef>
</TouchableHighlight>
</View>
<Text
style={

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ChannelInfoRow should match snapshot 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[MockFunction]}
>
<View
@@ -64,5 +64,5 @@ exports[`ChannelInfoRow should match snapshot 1`] = `
}
/>
</View>
</ForwardRef>
</TouchableHighlight>
`;

View File

@@ -101,7 +101,6 @@ export default class ChannelInfoHeader extends React.PureComponent {
BottomSheet.showBottomSheetWithOptions({
options: [actionText, cancelText],
cancelButtonIndex: 1,
}, (value) => {
if (value === 0) {
this.handleCopy(text);

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`screens/custom_status_suggestion should match snapshot 1`] = `
<ForwardRef
<TouchableOpacity
onPress={[Function]}
testID="custom_status_suggestion.In a meeting"
>
@@ -91,11 +91,11 @@ exports[`screens/custom_status_suggestion should match snapshot 1`] = `
</View>
</View>
</View>
</ForwardRef>
</TouchableOpacity>
`;
exports[`screens/custom_status_suggestion should match snapshot with separator and clear button 1`] = `
<ForwardRef
<TouchableOpacity
onPress={[Function]}
testID="custom_status_suggestion.In a meeting"
>
@@ -194,5 +194,5 @@ exports[`screens/custom_status_suggestion should match snapshot with separator a
/>
</View>
</View>
</ForwardRef>
</TouchableOpacity>
`;

View File

@@ -2,7 +2,7 @@
exports[`screens/clear_after_menu_item should match snapshot 1`] = `
<View>
<ForwardRef
<TouchableOpacity
onPress={[Function]}
testID="clear_after.menu_item."
>
@@ -68,13 +68,13 @@ exports[`screens/clear_after_menu_item should match snapshot 1`] = `
/>
</View>
</View>
</ForwardRef>
</TouchableOpacity>
</View>
`;
exports[`screens/clear_after_menu_item should match snapshot with separator and selected check 1`] = `
<View>
<ForwardRef
<TouchableOpacity
onPress={[Function]}
testID="clear_after.menu_item."
>
@@ -168,6 +168,6 @@ exports[`screens/clear_after_menu_item should match snapshot with separator and
}
}
/>
</ForwardRef>
</TouchableOpacity>
</View>
`;

View File

@@ -23,9 +23,10 @@ exports[`screens/date_time_selector should match snapshot 1`] = `
>
<View
accessibilityRole="button"
accessibilityState={Object {}}
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
@@ -68,9 +69,10 @@ exports[`screens/date_time_selector should match snapshot 1`] = `
</View>
<View
accessibilityRole="button"
accessibilityState={Object {}}
accessible={true}
collapsable={false}
focusable={true}
nativeID="animatedComponent"
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}

View File

@@ -148,7 +148,6 @@ exports[`ForgotPassword should match snapshot 1`] = `
}
/>
<TextInput
allowFontScaling={true}
autoCapitalize="none"
autoCorrect={false}
blurOnSubmit={false}
@@ -157,7 +156,6 @@ exports[`ForgotPassword should match snapshot 1`] = `
onChangeText={[Function]}
placeholder="Email"
placeholderTextColor="rgba(0,0,0,0.5)"
rejectResponderTermination={true}
style={
Object {
"alignSelf": "stretch",
@@ -271,7 +269,6 @@ exports[`ForgotPassword snapshot for error on failure of email regex 1`] = `
}
/>
<TextInput
allowFontScaling={true}
autoCapitalize="none"
autoCorrect={false}
blurOnSubmit={false}
@@ -280,7 +277,6 @@ exports[`ForgotPassword snapshot for error on failure of email regex 1`] = `
onChangeText={[Function]}
placeholder="Email"
placeholderTextColor="rgba(0,0,0,0.5)"
rejectResponderTermination={true}
style={
Object {
"alignSelf": "stretch",

View File

@@ -63,13 +63,13 @@ export default class Login extends PureComponent {
}
componentDidMount() {
Dimensions.addEventListener('change', this.orientationDidChange);
this.dimensionsListener = Dimensions.addEventListener('change', this.orientationDidChange);
this.setEmmUsernameIfAvailable();
}
componentWillUnmount() {
Dimensions.removeEventListener('change', this.orientationDidChange);
this.dimensionsListener?.remove();
}
goToChannel = () => {

View File

@@ -38,11 +38,11 @@ export default class LoginOptions extends PureComponent {
};
componentDidMount() {
Dimensions.addEventListener('change', this.orientationDidChange);
this.dimensionsListener = Dimensions.addEventListener('change', this.orientationDidChange);
}
componentWillUnmount() {
Dimensions.removeEventListener('change', this.orientationDidChange);
this.dimensionsListener?.remove();
}
goToLogin = preventDoubleTap(async () => {

View File

@@ -47,13 +47,13 @@ export default class Mfa extends PureComponent {
componentDidMount() {
if (Platform.OS === 'android') {
Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
this.keyboardListener = Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard);
}
}
componentWillUnmount() {
if (Platform.OS === 'android') {
Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard);
this.keyboardListener?.remove();
}
}

View File

@@ -430,7 +430,6 @@ export default class MoreChannels extends PureComponent {
BottomSheet.showBottomSheetWithOptions({
options,
cancelButtonIndex: 3,
title: titleText,
}, (value) => {
let typeOfChannels;

View File

@@ -5,8 +5,10 @@ exports[`OptionModalList should match snapshot 1`] = `
style={
Object {
"alignItems": "center",
"flex": 1,
"bottom": 0,
"height": "100%",
"justifyContent": "flex-end",
"width": "100%",
}
}
>
@@ -27,7 +29,7 @@ exports[`OptionModalList should match snapshot 1`] = `
"borderTopLeftRadius": 12,
"borderTopRightRadius": 12,
"paddingBottom": 25,
"paddingVertical": 10,
"paddingTop": 10,
},
]
}
@@ -66,7 +68,7 @@ exports[`OptionModalList should match snapshot 1`] = `
</Text>
</View>
<View>
<ForwardRef
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -107,10 +109,10 @@ exports[`OptionModalList should match snapshot 1`] = `
]
}
/>
</ForwardRef>
</TouchableOpacity>
</View>
<View>
<ForwardRef
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -151,7 +153,7 @@ exports[`OptionModalList should match snapshot 1`] = `
]
}
/>
</ForwardRef>
</TouchableOpacity>
</View>
</View>
</View>

View File

@@ -92,7 +92,7 @@ export default class OptionsModal extends PureComponent {
return (
<TouchableWithoutFeedback onPress={this.handleCancel}>
<View style={style.wrapper}>
<AnimatedView style={{height: this.props.deviceHeight, left: 0, top: this.state.top, width: this.props.deviceWidth}}>
<AnimatedView style={{top: this.state.top}}>
<OptionsModalList
items={items}
onCancelPress={this.handleCancel}

View File

@@ -4,7 +4,6 @@
import PropTypes from 'prop-types';
import React, {PureComponent} from 'react';
import {
Platform,
StyleSheet,
Text,
TouchableOpacity,
@@ -157,16 +156,8 @@ const style = StyleSheet.create({
backgroundColor: 'white',
borderTopLeftRadius: 12,
borderTopRightRadius: 12,
paddingVertical: 10,
...Platform.select({
ios: {
paddingBottom: 25,
},
android: {
marginBottom: -10,
},
}),
paddingTop: 10,
paddingBottom: 25,
},
optionIcon: {
color: 'rgba(61, 60, 64, 0.64)',
@@ -197,9 +188,11 @@ const style = StyleSheet.create({
paddingBottom: 10,
},
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'flex-end',
bottom: 0,
height: '100%',
width: '100%',
},
wrapper: {
maxWidth: 450,

View File

@@ -50,7 +50,7 @@ exports[`Permalink should match snapshot 1`] = `
}
}
>
<ForwardRef
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -66,7 +66,7 @@ exports[`Permalink should match snapshot 1`] = `
name="close"
size={20}
/>
</ForwardRef>
</TouchableOpacity>
<View
style={
Object {

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ReactionHeaderItem should match snapshot 1`] = `
<ForwardRef
<TouchableOpacity
onPress={[Function]}
style={
Array [
@@ -47,7 +47,7 @@ exports[`ReactionHeaderItem should match snapshot 1`] = `
3
</Text>
</React.Fragment>
</ForwardRef>
</TouchableOpacity>
`;
exports[`ReactionHeaderItem should match snapshot, renderContent 1`] = `

View File

@@ -20,7 +20,7 @@ exports[`ReactionRow should match snapshot, renderContent 1`] = `
}
}
>
<ForwardRef
<TouchableOpacity
onPress={[Function]}
>
<View
@@ -38,7 +38,7 @@ exports[`ReactionRow should match snapshot, renderContent 1`] = `
userId="user_id"
/>
</View>
</ForwardRef>
</TouchableOpacity>
</View>
<Text
ellipsizeMode="tail"

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Search RecentItem should match snapshot and respond to events 1`] = `
<ForwardRef
<TouchableHighlight
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
>
@@ -80,5 +80,5 @@ exports[`Search RecentItem should match snapshot and respond to events 1`] = `
</Text>
</View>
</View>
</ForwardRef>
</TouchableHighlight>
`;

View File

@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Search RecentItem should match snapshot and respond to events 1`] = `
<ForwardRef
<TouchableHighlight
key="test"
onPress={[Function]}
underlayColor="rgba(40,66,123,0.5)"
@@ -30,7 +30,7 @@ exports[`Search RecentItem should match snapshot and respond to events 1`] = `
>
test
</Text>
<ForwardRef
<TouchableOpacity
onPress={[Function]}
style={
Object {
@@ -47,7 +47,7 @@ exports[`Search RecentItem should match snapshot and respond to events 1`] = `
name="close-circle-outline"
size={20}
/>
</ForwardRef>
</TouchableOpacity>
</View>
</ForwardRef>
</TouchableHighlight>
`;

View File

@@ -86,15 +86,8 @@ exports[`Search should match snapshot 1`] = `
>
<SectionList
ListFooterComponent={[Function]}
data={Array []}
disableVirtualization={false}
horizontal={false}
initialNumToRender={10}
keyExtractor={[Function]}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={10}
onEndReachedThreshold={2}
onLayout={[Function]}
onScroll={[Function]}
onViewableItemsChanged={[Function]}
@@ -158,8 +151,6 @@ exports[`Search should match snapshot 1`] = `
}
}
testID="search.results_list"
updateCellsBatchingPeriod={50}
windowSize={21}
/>
</RNCSafeAreaView>
<Connect(Autocomplete)

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