Compare commits

...

16 Commits

Author SHA1 Message Date
Elias Nahum
d451cf6bde Use commonmark & patch it instead of a fork (#5498) 2021-07-20 12:25:15 -04:00
Elias Nahum
38fa757942 Bump Version to 1.45.1 and Build to 364 (#5558)
* Bump app build number to  364

* Bump app version number to  1.45.1
2021-07-20 12:06:45 -04:00
Mattermost Build
ad249e9fe4 Fix crash on markdown emoji with hardbreak parser (#5547) (#5549)
(cherry picked from commit 90cce88358)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-20 12:00:11 -04:00
Mattermost Build
44708f5bba Properly group Android push notifications by channel (#5548) (#5557)
(cherry picked from commit ba78bc9d00)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-20 11:47:15 -04:00
Weblate (bot)
ca308dee84 Translations update from Weblate (#5532)
* Translated using Weblate (German)

Currently translated at 99.7% (707 of 709 strings)

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

* Translated using Weblate (Bulgarian)

Currently translated at 100.0% (709 of 709 strings)

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

Translated using Weblate (Bulgarian)

Currently translated at 100.0% (709 of 709 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (709 of 709 strings)

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

Translated using Weblate (Hungarian)

Currently translated at 100.0% (709 of 709 strings)

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

Translated using Weblate (Hungarian)

Currently translated at 100.0% (709 of 709 strings)

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

Translated using Weblate (Hungarian)

Currently translated at 100.0% (709 of 709 strings)

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

* Translated using Weblate (Turkish)

Currently translated at 100.0% (709 of 709 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 100.0% (709 of 709 strings)

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

* Translated using Weblate (English (Australia))

Currently translated at 100.0% (709 of 709 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (709 of 709 strings)

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

Translated using Weblate (German)

Currently translated at 99.5% (706 of 709 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (709 of 709 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (709 of 709 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (709 of 709 strings)

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

Co-authored-by: jprusch <rs@schaeferbarthold.de>
Co-authored-by: Nikolai Zahariev <nikolaiz@yahoo.com>
Co-authored-by: Tóth Csaba // Online ERP Hungary Kft <csaba.toth@online-erp.hu>
Co-authored-by: Kaya Zeren <kayazeren@gmail.com>
Co-authored-by: Tom De Moor <tom@controlaltdieliet.be>
Co-authored-by: Matthew Williams <Matthew.Williams@outlook.com.au>
Co-authored-by: Markus Hermann <markus.hermann@uni-marburg.de>
Co-authored-by: aeomin <lin@aeomin.net>
Co-authored-by: Elias  Nahum <elias@mattermost.com>
Co-authored-by: kaakaa <stooner.hoe@gmail.com>
2021-07-13 09:44:16 +02:00
Mattermost Build
d640a5643d Bump app build number to 363 (#5537) (#5538)
(cherry picked from commit d7cd660ab7)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-12 20:07:30 -04:00
Mattermost Build
da575c7cb1 1.45 Release testing bug fixes (#5529) (#5536)
* MM-36908 display unicode emoji in text field after selecting from autocomple

* MM-36935 Fix android crash when ammending search in Sidebar Jump to

* MM-36929 & MM-36928 fix notification badge resetting on new notification

* MM-36920 Fix android push notification issues

* MM-36922 Edit profile image

* Fix crash when opening Android test notification

(cherry picked from commit 78156ee75b)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-12 16:13:54 -04:00
Mattermost Build
962a1759a5 MM-36881 Fix scroll perf degradation with pull to refresh enabled (#5525) (#5535)
* Fix scroll perf degradation with pull to refresh enabled

* Update app/components/post_list/post_list_refresh_control.tsx

Co-authored-by: Anurag Shivarathri <anurag6713@gmail.com>

Co-authored-by: Anurag Shivarathri <anurag6713@gmail.com>
(cherry picked from commit 58d9c2e9f0)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-12 16:13:42 -04:00
Mattermost Build
50f616f5d7 MM-36900 Fix mm emoji (#5526) (#5528) 2021-07-10 12:34:26 +02:00
Mattermost Build
1a20677eba Bump app build number to 362 (#5522) (#5523)
(cherry picked from commit 9fafc3be5b)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-07 21:25:40 -04:00
Mattermost Build
5b5b7bc620 Bring back refresh control for channels and threads (#5511) (#5520)
(cherry picked from commit 073b836beb)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-07 13:04:07 -04:00
Mattermost Build
456ab53ecf Bump Version to 1.45.0 and build to 361 (#5518) (#5519)
* Bump app build number to  361

* Bump app version number to  1.45.0

(cherry picked from commit 6984923a4c)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-07 12:56:07 -04:00
Mattermost Build
9c87835802 Load other teams you can join when deleting cache (#5516) (#5517)
(cherry picked from commit 883399f13d)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-07 12:38:03 -04:00
Mattermost Build
11cc2e57f5 MM-36808 fix auto responder posts (#5505) (#5515) 2021-07-07 06:54:31 -04:00
Mattermost Build
ac5914ef15 MM-36745 Fix animation for showModalOverCurrentContext (#5507) (#5510)
(cherry picked from commit df26a363bc)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2021-07-06 14:31:15 -04:00
Mattermost Build
9c15ae1c5b use user skin preferences for emoji (#5502) (#5509) 2021-07-06 20:13:23 +02:00
65 changed files with 8741 additions and 1558 deletions

View File

@@ -132,8 +132,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60"
versionCode 360
versionName "1.44.1"
versionCode 364
versionName "1.45.1"
multiDexEnabled = true
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -1,7 +1,9 @@
package com.mattermost.rnbeta;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
@@ -12,6 +14,7 @@ import androidx.core.app.NotificationManagerCompat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -23,12 +26,15 @@ import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_
import com.mattermost.react_native_interface.ResolvePromise;
import org.json.JSONArray;
import org.json.JSONObject;
public class CustomPushNotification extends PushNotification {
private static final String PUSH_NOTIFICATIONS = "PUSH_NOTIFICATIONS";
private static final String PUSH_TYPE_MESSAGE = "message";
private static final String PUSH_TYPE_CLEAR = "clear";
private static final String PUSH_TYPE_SESSION = "session";
private final static Map<String, List<Integer>> notificationsInChannel = new HashMap<>();
private static final String NOTIFICATIONS_IN_CHANNEL = "notificationsInChannel";
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
@@ -37,12 +43,14 @@ public class CustomPushNotification extends PushNotification {
public static void cancelNotification(Context context, String channelId, Integer notificationId) {
if (!android.text.TextUtils.isEmpty(channelId)) {
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(context);
List<Integer> notifications = notificationsInChannel.get(channelId);
if (notifications == null) {
return;
}
notifications.remove(notificationId);
saveNotificationsMap(context, notificationsInChannel);
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.cancel(notificationId);
}
@@ -50,25 +58,27 @@ public class CustomPushNotification extends PushNotification {
public static void clearChannelNotifications(Context context, String channelId) {
if (!android.text.TextUtils.isEmpty(channelId)) {
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(context);
List<Integer> notifications = notificationsInChannel.get(channelId);
if (notifications == null) {
return;
}
notificationsInChannel.remove(channelId);
saveNotificationsMap(context, notificationsInChannel);
if (context != null) {
for (final Integer notificationId : notifications) {
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.cancel(notificationId);
}
for (final Integer notificationId : notifications) {
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.cancel(notificationId);
}
}
}
public static void clearAllNotifications(Context context) {
notificationsInChannel.clear();
if (context != null) {
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(context);
notificationsInChannel.clear();
saveNotificationsMap(context, notificationsInChannel);
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.cancelAll();
}
@@ -106,22 +116,38 @@ public class CustomPushNotification extends PushNotification {
});
}
if (channelId != null) {
synchronized (notificationsInChannel) {
List<Integer> list = notificationsInChannel.get(channelId);
if (list == null) {
list = Collections.synchronizedList(new ArrayList(0));
}
list.add(0, notificationId);
notificationsInChannel.put(channelId, list);
}
}
switch (type) {
case PUSH_TYPE_MESSAGE:
case PUSH_TYPE_SESSION:
super.postNotification(notificationId);
boolean createSummary = type.equals(PUSH_TYPE_MESSAGE);
if (!mAppLifecycleFacade.isAppVisible()) {
if (type.equals(PUSH_TYPE_MESSAGE)) {
if (channelId != null) {
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(mContext);
List<Integer> list = notificationsInChannel.get(channelId);
if (list == null) {
list = Collections.synchronizedList(new ArrayList(0));
}
list.add(0, notificationId);
if (list.size() > 1) {
createSummary = false;
}
if (createSummary) {
// Add the summary notification id as well
list.add(0, notificationId + 1);
}
notificationsInChannel.put(channelId, list);
saveNotificationsMap(mContext, notificationsInChannel);
}
}
buildNotification(notificationId, createSummary);
} else {
notifyReceivedToJS();
}
break;
case PUSH_TYPE_CLEAR:
clearChannelNotifications(mContext, channelId);
@@ -139,18 +165,41 @@ public class CustomPushNotification extends PushNotification {
Bundle data = mNotificationProps.asBundle();
final String channelId = data.getString("channel_id");
final Integer notificationId = data.getString("post_id").hashCode();
final String postId = data.getString("post_id");
Integer notificationId = CustomPushNotificationHelper.MESSAGE_NOTIFICATION_ID;
if (postId != null) {
notificationId = postId.hashCode();
}
if (channelId != null) {
Map<String, List<Integer>> notificationsInChannel = loadNotificationsMap(mContext);
List<Integer> notifications = notificationsInChannel.get(channelId);
notifications.remove(notificationId);
saveNotificationsMap(mContext, notificationsInChannel);
clearChannelNotifications(mContext, channelId);
}
}
private void buildNotification(Integer notificationId, boolean createSummary) {
final PendingIntent pendingIntent = super.getCTAPendingIntent();
final Notification notification = buildNotification(pendingIntent);
if (createSummary) {
final Notification summary = getNotificationSummaryBuilder(pendingIntent).build();
super.postNotification(summary, notificationId + 1);
}
super.postNotification(notification, notificationId);
}
@Override
protected NotificationCompat.Builder getNotificationBuilder(PendingIntent intent) {
Bundle bundle = mNotificationProps.asBundle();
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle);
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle, false);
}
protected NotificationCompat.Builder getNotificationSummaryBuilder(PendingIntent intent) {
Bundle bundle = mNotificationProps.asBundle();
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle, true);
}
private void notificationReceiptDelivery(String ackId, String postId, String type, boolean isIdLoaded, ResolvePromise promise) {
@@ -160,4 +209,43 @@ public class CustomPushNotification extends PushNotification {
private void notifyReceivedToJS() {
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
}
private static void saveNotificationsMap(Context context, Map<String, List<Integer>> inputMap) {
SharedPreferences pSharedPref = context.getSharedPreferences(PUSH_NOTIFICATIONS, Context.MODE_PRIVATE);
if (pSharedPref != null && context != null) {
JSONObject json = new JSONObject(inputMap);
String jsonString = json.toString();
SharedPreferences.Editor editor = pSharedPref.edit();
editor.remove(NOTIFICATIONS_IN_CHANNEL).commit();
editor.putString(NOTIFICATIONS_IN_CHANNEL, jsonString);
editor.commit();
}
}
private static Map<String, List<Integer>> loadNotificationsMap(Context context) {
Map<String, List<Integer>> outputMap = new HashMap<>();
if (context != null) {
SharedPreferences pSharedPref = context.getSharedPreferences(PUSH_NOTIFICATIONS, Context.MODE_PRIVATE);
try {
if (pSharedPref != null) {
String jsonString = pSharedPref.getString(NOTIFICATIONS_IN_CHANNEL, (new JSONObject()).toString());
JSONObject json = new JSONObject(jsonString);
Iterator<String> keysItr = json.keys();
while (keysItr.hasNext()) {
String key = keysItr.next();
JSONArray array = json.getJSONArray(key);
List<Integer> values = new ArrayList<>();
for (int i = 0; i < array.length(); ++i) {
values.add(array.getInt(i));
}
outputMap.put(key, values);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return outputMap;
}
}

View File

@@ -137,7 +137,7 @@ public class CustomPushNotificationHelper {
.addAction(replyAction);
}
public static NotificationCompat.Builder createNotificationBuilder(Context context, PendingIntent intent, Bundle bundle) {
public static NotificationCompat.Builder createNotificationBuilder(Context context, PendingIntent intent, Bundle bundle, boolean createSummary) {
final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL_HIGH_IMPORTANCE_ID);
String channelId = bundle.getString("channel_id");
@@ -148,7 +148,7 @@ public class CustomPushNotificationHelper {
addNotificationExtras(notification, bundle);
setNotificationIcons(context, notification, bundle);
setNotificationMessagingStyle(context, notification, bundle);
setNotificationGroup(notification, channelId);
setNotificationGroup(notification, channelId, createSummary);
setNotificationBadgeType(notification);
setNotificationSound(notification, notificationPreferences);
setNotificationVibrate(notification, notificationPreferences);
@@ -373,10 +373,13 @@ public class CustomPushNotificationHelper {
notification.setStyle(messagingStyle);
}
private static void setNotificationGroup(NotificationCompat.Builder notification, String channelId) {
notification
.setGroup(channelId)
.setGroupSummary(true);
private static void setNotificationGroup(NotificationCompat.Builder notification, String channelId, boolean setAsSummary) {
notification.setGroup(channelId);
if (setAsSummary) {
// if this is the first notification for the channel then set as summary, otherwise skip
notification.setGroupSummary(true);
}
}
private static void setNotificationIcons(Context context, NotificationCompat.Builder notification, Bundle bundle) {

View File

@@ -4,7 +4,6 @@ import android.content.Context;
import android.content.RestrictionsManager;
import android.os.Bundle;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.io.File;
import java.util.HashMap;
import java.util.List;
@@ -101,7 +100,7 @@ private final ReactNativeHost mReactNativeHost =
@Override
protected JSIModulePackage getJSIModulePackage() {
return new CustomMMKVJSIModulePackage();
return (JSIModulePackage) new CustomMMKVJSIModulePackage();
}
};

View File

@@ -18,7 +18,14 @@ public class NotificationDismissService extends IntentService {
final Context context = getApplicationContext();
final Bundle bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
final String channelId = bundle.getString("channel_id");
final Integer notificationId = bundle.getString("post_id").hashCode();
final String postId = bundle.getString("post_id");
int notificationId = CustomPushNotificationHelper.MESSAGE_NOTIFICATION_ID;
if (postId != null) {
notificationId = postId.hashCode();
} else if (channelId != null) {
notificationId = channelId.hashCode();
}
CustomPushNotification.cancelNotification(context, channelId, notificationId);
Log.i("ReactNative", "Dismiss notification");
}

View File

@@ -9,11 +9,13 @@ import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.app.Person;
import java.io.IOException;
import java.util.Objects;
import okhttp3.Call;
import okhttp3.MediaType;
@@ -21,7 +23,6 @@ import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.internal.annotations.EverythingIsNonNull;
import org.json.JSONObject;
import org.json.JSONException;
@@ -88,21 +89,19 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
client.newCall(request).enqueue(new okhttp3.Callback() {
@Override
@EverythingIsNonNull
public void onFailure(Call call, IOException e) {
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.i("ReactNative", String.format("Reply FAILED exception %s", e.getMessage()));
onReplyFailed(notificationId);
}
@Override
@EverythingIsNonNull
public void onResponse(Call call, final Response response) throws IOException {
public void onResponse(@NonNull Call call, @NonNull final Response response) throws IOException {
if (response.isSuccessful()) {
onReplySuccess(notificationId, message);
Log.i("ReactNative", "Reply SUCCESS");
} else {
assert response.body() != null;
Log.i("ReactNative", String.format("Reply FAILED status %s BODY %s", response.code(), response.body().string()));
Log.i("ReactNative", String.format("Reply FAILED status %s BODY %s", response.code(), Objects.requireNonNull(response.body()).string()));
onReplyFailed(notificationId);
}
}
@@ -133,7 +132,7 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
final Intent cta = new Intent(mContext, ProxyService.class);
final PushNotificationProps notificationProps = new PushNotificationProps(bundle);
final PendingIntent pendingIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, cta, notificationProps);
NotificationCompat.Builder builder = CustomPushNotificationHelper.createNotificationBuilder(mContext, pendingIntent, bundle);
NotificationCompat.Builder builder = CustomPushNotificationHelper.createNotificationBuilder(mContext, pendingIntent, bundle, false);
Notification notification = builder.build();
NotificationCompat.MessagingStyle messagingStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(notification);
assert messagingStyle != null;

View File

@@ -293,7 +293,44 @@ export function showModal(name, title, passProps = {}, options = {}) {
export function showModalOverCurrentContext(name, passProps = {}, options = {}) {
const title = '';
const animationsEnabled = (Platform.OS === 'android').toString();
let animations;
switch (Platform.OS) {
case 'android':
animations = {
showModal: {
waitForRender: true,
alpha: {
from: 0,
to: 1,
duration: 250,
},
},
dismissModal: {
alpha: {
from: 1,
to: 0,
duration: 250,
},
},
};
break;
default:
animations = {
showModal: {
enabled: false,
enter: {},
exit: {},
},
dismissModal: {
enabled: false,
enter: {},
exit: {},
},
};
break;
}
const defaultOptions = {
modalPresentationStyle: 'overCurrentContext',
layout: {
@@ -304,25 +341,7 @@ export function showModalOverCurrentContext(name, passProps = {}, options = {})
visible: false,
height: 0,
},
animations: {
showModal: {
waitForRender: true,
enabled: animationsEnabled,
alpha: {
from: 0,
to: 1,
duration: 250,
},
},
dismissModal: {
enabled: animationsEnabled,
alpha: {
from: 1,
to: 0,
duration: 250,
},
},
},
animations,
};
const mergeOptions = merge(defaultOptions, options);

View File

@@ -301,7 +301,6 @@ describe('@actions/navigation', () => {
test('showModalOverCurrentContext should call Navigation.showModal', () => {
const showModal = jest.spyOn(Navigation, 'showModal');
const animationsEnabled = (Platform.OS === 'android').toString();
const showModalOverCurrentContextTitle = '';
const showModalOverCurrentContextOptions = {
modalPresentationStyle: 'overCurrentContext',
@@ -315,21 +314,14 @@ describe('@actions/navigation', () => {
},
animations: {
showModal: {
waitForRender: true,
enabled: animationsEnabled,
alpha: {
from: 0,
to: 1,
duration: 250,
},
enabled: false,
enter: {},
exit: {},
},
dismissModal: {
enabled: animationsEnabled,
alpha: {
from: 1,
to: 0,
duration: 250,
},
enabled: false,
enter: {},
exit: {},
},
},
};

View File

@@ -503,16 +503,7 @@ export function closeGMChannel(channel) {
export function refreshChannelWithRetry(channelId) {
return async (dispatch) => {
dispatch(setChannelRefreshing(true));
const posts = await dispatch(fetchPostActionWithRetry(getPosts(channelId)));
const actions = [setChannelRefreshing(false)];
if (posts) {
actions.push(setChannelRetryFailed(false));
}
dispatch(batchActions(actions, 'BATCH_REEFRESH_CHANNEL'));
return posts;
return dispatch(fetchPostActionWithRetry(getPosts(channelId)));
};
}

View File

@@ -28,6 +28,7 @@ export function setProfileImageUri(imageUri = '') {
export function removeProfileImage(user) {
return async (dispatch) => {
const result = await dispatch(setDefaultProfileImage(user));
dispatch(setProfileImageUri());
return result;
};
}

View File

@@ -15,7 +15,7 @@ import DeviceInfo from 'react-native-device-info';
import AndroidOpenSettings from 'react-native-android-open-settings';
import DocumentPicker from 'react-native-document-picker';
import ImagePicker from 'react-native-image-picker';
import {launchImageLibrary, launchCamera} from 'react-native-image-picker';
import Permissions from 'react-native-permissions';
import {showModalOverCurrentContext} from '@actions/navigation';
@@ -153,58 +153,35 @@ export default class AttachmentButton extends PureComponent {
};
attachFileFromCamera = async (source, mediaType) => {
const {formatMessage} = this.context.intl;
const {title, text} = this.getPermissionDeniedMessage('camera', mediaType);
const options = {
quality: 0.8,
videoQuality: 'high',
noData: true,
mediaType,
storageOptions: {
cameraRoll: true,
waitUntilSaved: true,
},
permissionDenied: {
title,
text,
reTryTitle: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
},
saveToPhotos: true,
};
const hasCameraPermission = await this.hasPhotoPermission(source, mediaType);
if (hasCameraPermission) {
ImagePicker.launchCamera(options, (response) => {
launchCamera(options, async (response) => {
StatusBar.setHidden(false);
emmProvider.inBackgroundSince = null;
if (response.error || response.didCancel) {
return;
}
this.uploadFiles([response]);
const files = await this.getFilesFromResponse(response);
this.uploadFiles(files);
});
}
};
attachFileFromLibrary = async () => {
const {formatMessage} = this.context.intl;
const {title, text} = this.getPermissionDeniedMessage('photo');
const options = {
quality: 0.8,
noData: true,
permissionDenied: {
title,
text,
reTryTitle: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
},
mediaType: 'mixed',
includeBase64: false,
};
if (Platform.OS === 'ios') {
@@ -214,14 +191,15 @@ export default class AttachmentButton extends PureComponent {
const hasPhotoPermission = await this.hasPhotoPermission('photo', 'photo');
if (hasPhotoPermission) {
ImagePicker.launchImageLibrary(options, (response) => {
launchImageLibrary(options, async (response) => {
StatusBar.setHidden(false);
emmProvider.inBackgroundSince = null;
if (response.error || response.didCancel) {
return;
}
this.uploadFiles([response]);
const files = await this.getFilesFromResponse(response);
this.uploadFiles(files);
});
}
};
@@ -231,30 +209,20 @@ export default class AttachmentButton extends PureComponent {
};
attachVideoFromLibraryAndroid = () => {
const {formatMessage} = this.context.intl;
const {title, text} = this.getPermissionDeniedMessage('video');
const options = {
videoQuality: 'high',
mediaType: 'video',
noData: true,
permissionDenied: {
title,
text,
reTryTitle: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
},
};
ImagePicker.launchImageLibrary(options, (response) => {
launchImageLibrary(options, async (response) => {
emmProvider.inBackgroundSince = null;
if (response.error || response.didCancel) {
return;
}
this.uploadFiles([response]);
const files = await this.getFilesFromResponse(response);
this.uploadFiles(files);
});
};
@@ -286,6 +254,28 @@ export default class AttachmentButton extends PureComponent {
}
};
getFilesFromResponse = async (response) => {
const files = [];
const file = response.assets[0];
if (Platform.OS === 'android') {
// For android we need to retrieve the realPath in case the file being imported is from the cloud
const uri = (await ShareExtension.getFilePath(file.uri)).filePath;
const type = file.type || lookupMimeType(uri);
let fileName = file.fileName;
if (type.includes('video/')) {
fileName = uri.split('\\').pop().split('/').pop();
}
if (uri) {
files.push({...file, fileName, uri, type});
}
} else {
files.push(file);
}
return files;
}
hasPhotoPermission = async (source, mediaType = '') => {
if (Platform.OS === 'ios') {
const {formatMessage} = this.context.intl;

View File

@@ -1403,6 +1403,7 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
"massage_man",
"massage_woman",
"mate_drink",
"mattermost",
"mauritania",
"mauritius",
"mayotte",
@@ -3742,6 +3743,7 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
"massage_man",
"massage_woman",
"mate_drink",
"mattermost",
"mauritania",
"mauritius",
"mayotte",

View File

@@ -13,7 +13,6 @@ import Fuse from 'fuse.js';
import Emoji from '@components/emoji';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {BuiltInEmojis} from '@utils/emojis';
import {getEmojiByName, compareEmojis} from '@utils/emoji_utils';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
@@ -110,8 +109,8 @@ export default class EmojiSuggestion extends PureComponent {
}
const emojiData = getEmojiByName(emoji);
if (emojiData?.filename && !BuiltInEmojis.includes(emojiData.filename)) {
const codeArray = emojiData.filename.split('-');
if (emojiData?.image && emojiData.category !== 'custom') {
const codeArray = emojiData.image.split('-');
const code = codeArray.reduce((acc, c) => {
return acc + String.fromCodePoint(parseInt(c, 16));
}, '');
@@ -126,7 +125,7 @@ export default class EmojiSuggestion extends PureComponent {
onChangeText(completedDraft);
if (Platform.OS === 'ios' && (!emojiData?.filename || BuiltInEmojis.includes(emojiData?.filename))) {
if (Platform.OS === 'ios' && (!emojiData?.filename || emojiData.category !== 'custom')) {
// This is the second part of the hack were we replace the double : with just one
// after the auto correct vanished
setTimeout(() => {

View File

@@ -3,10 +3,7 @@
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {
Text,
View,
} from 'react-native';
import {Platform, Text, View} from 'react-native';
import {injectIntl, intlShape} from 'react-intl';
import {displayUsername} from '@mm-redux/utils/user_utils';
@@ -386,6 +383,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
marginHorizontal: 12,
marginBottom: 12,
overflow: 'hidden',
...Platform.select({
android: {
scaleY: -1,
},
}),
},
displayName: {
color: theme.centerChannelColor,

View File

@@ -11,6 +11,8 @@ import {
} from 'react-native';
import FastImage, {ImageStyle} from 'react-native-fast-image';
const assetImages = new Map([['mattermost.png', require('@assets/images/emojis/mattermost.png')]]);
type Props = {
/*
@@ -23,6 +25,11 @@ type Props = {
*/
imageUrl: string;
/*
* asset name in case it is bundled with the app
*/
assetImage: string;
/*
* Set if this is a custom emoji.
*/
@@ -45,6 +52,7 @@ const Emoji: React.FC<Props> = (props: Props) => {
customEmojiStyle,
displayTextOnly,
imageUrl,
assetImage,
literal,
unicode,
testID,
@@ -88,6 +96,24 @@ const Emoji: React.FC<Props> = (props: Props) => {
);
}
if (assetImage) {
const key = Platform.OS === 'android' ? (`${assetImage}-${height}-${width}`) : null;
const image = assetImages.get(assetImage);
if (!image) {
return null;
}
return (
<FastImage
key={key}
source={image}
style={[customEmojiStyle, {width, height}]}
resizeMode={FastImage.resizeMode.contain}
testID={testID}
/>
);
}
if (!imageUrl) {
return null;
}

View File

@@ -26,11 +26,17 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
let imageUrl = '';
let unicode;
let assetImage = '';
let isCustomEmoji = false;
let displayTextOnly = false;
if (EmojiIndicesByAlias.has(emojiName)) {
const emoji = Emojis[EmojiIndicesByAlias.get(emojiName)!];
unicode = emoji.image;
if (emoji.category === 'custom') {
assetImage = emoji.fileName;
isCustomEmoji = true;
} else {
unicode = emoji.image;
}
} else if (customEmojis.has(emojiName) && serverUrl) {
const emoji = customEmojis.get(emojiName);
imageUrl = Client4.getCustomEmojiImageUrl(emoji!.id);
@@ -45,6 +51,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
return {
imageUrl,
assetImage,
isCustomEmoji,
displayTextOnly,
unicode,

View File

@@ -12861,7 +12861,25 @@ exports[`components/emoji_picker/emoji_picker.ios should match snapshot 1`] = `
"key": "flags",
},
Object {
"data": Array [],
"data": Array [
Object {
"items": Array [
Object {
"aliases": Array [
"mattermost",
],
"name": "mattermost",
},
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
],
"key": "custom-0",
},
],
"defaultMessage": undefined,
"icon": "emoticon-custom-outline",
"id": "emoji_picker.custom",

View File

@@ -31,3 +31,164 @@ exports[`MarkdownEmoji should match snapshot 1`] = `
</Unknown>
</Unknown>
`;
exports[`MarkdownEmoji should render with hardbreaks 1`] = `
<Unknown
context={Array []}
first={true}
last={true}
literal={null}
nodeKey="22"
>
<Unknown
context={
Array [
"paragraph",
]
}
emojiName="fire"
literal=":fire:"
nodeKey="4"
>
<Unknown
context={
Array [
"paragraph",
"emoji",
]
}
literal=":fire:"
nodeKey="3"
/>
</Unknown>
<Unknown
context={
Array [
"paragraph",
]
}
literal=" "
nodeKey="5"
/>
<Unknown
context={
Array [
"paragraph",
]
}
emojiName="fire"
literal=":fire:"
nodeKey="8"
>
<Unknown
context={
Array [
"paragraph",
"emoji",
]
}
literal=":fire:"
nodeKey="7"
/>
</Unknown>
<Unknown
context={
Array [
"paragraph",
]
}
literal=""
nodeKey="9"
/>
<Unknown
context={
Array [
"paragraph",
]
}
literal={null}
nodeKey="10"
/>
<Unknown
context={
Array [
"paragraph",
]
}
emojiName="fire"
literal=":fire:"
nodeKey="13"
>
<Unknown
context={
Array [
"paragraph",
"emoji",
]
}
literal=":fire:"
nodeKey="12"
/>
</Unknown>
<Unknown
context={
Array [
"paragraph",
]
}
literal=" "
nodeKey="14"
/>
<Unknown
context={
Array [
"paragraph",
]
}
emojiName="fire"
literal=":fire:"
nodeKey="17"
>
<Unknown
context={
Array [
"paragraph",
"emoji",
]
}
literal=":fire:"
nodeKey="16"
/>
</Unknown>
<Unknown
context={
Array [
"paragraph",
]
}
literal=" "
nodeKey="18"
/>
<Unknown
context={
Array [
"paragraph",
]
}
emojiName="fire"
literal=":fire:"
nodeKey="21"
>
<Unknown
context={
Array [
"paragraph",
"emoji",
]
}
literal=":fire:"
nodeKey="20"
/>
</Unknown>
</Unknown>
`;

View File

@@ -39,6 +39,7 @@ export default class MarkdownEmoji extends PureComponent {
paragraph: this.renderParagraph,
document: this.renderParagraph,
text: this.renderText,
hardbreak: this.renderNewLine,
},
});
};
@@ -73,10 +74,14 @@ export default class MarkdownEmoji extends PureComponent {
renderText = ({context, literal}) => {
const style = this.computeTextStyle(this.props.baseTextStyle, context);
return <Text style={style}>{literal}</Text>;
};
renderNewLine = ({context}) => {
const style = this.computeTextStyle(this.props.baseTextStyle, context);
return <Text style={style}>{'\n'}</Text>;
}
renderEditedIndicator = ({context}) => {
let spacer = '';
if (context[0] === 'paragraph') {
@@ -101,7 +106,7 @@ export default class MarkdownEmoji extends PureComponent {
};
render() {
const ast = this.parser.parse(this.props.value);
const ast = this.parser.parse(this.props.value.replace(/\n*$/, ''));
if (this.props.isEdited) {
const editIndicatorNode = new Node('edited_indicator');

View File

@@ -24,4 +24,17 @@ describe('MarkdownEmoji', () => {
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should render with hardbreaks', () => {
const wrapper = shallow(
<MarkdownEmoji
{...baseProps}
value={`:fire: :fire:
:fire: :fire: :fire:
`}
/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
});
});

View File

@@ -3,7 +3,7 @@
import React from 'react';
import {injectIntl, intlShape} from 'react-intl';
import {Keyboard, View} from 'react-native';
import {Keyboard, StyleProp, View, ViewStyle} from 'react-native';
import {showModalOverCurrentContext} from '@actions/navigation';
import Markdown from '@components/markdown';
@@ -31,6 +31,7 @@ type Props = {
testID?: string;
theme: Theme;
usernamesById: Record<string, string>;
style?: StyleProp<ViewStyle>;
}
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
@@ -87,7 +88,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
const CombinedUserActivity = ({
canDelete, currentUserId, currentUsername, intl, post,
showJoinLeave, testID, theme, usernamesById,
showJoinLeave, testID, theme, usernamesById, style,
}: Props) => {
const itemTestID = `${testID}.${post.id}`;
const textStyles = getMarkdownTextStyles(theme);
@@ -215,6 +216,7 @@ const CombinedUserActivity = ({
return (
<View
style={style}
testID={testID}
>
<TouchableWithFeedback

View File

@@ -5,6 +5,7 @@ import {connect} from 'react-redux';
import {handleSelectChannelByName, refreshChannelWithRetry} from '@actions/views/channel';
import {closePermalink, showPermalink} from '@actions/views/permalink';
import {getPostThread} from '@actions/views/post';
import {setDeepLinkURL} from '@actions/views/root';
import {getConfig, getCurrentUrl} from '@mm-redux/selectors/entities/general';
import {getTheme} from '@mm-redux/selectors/entities/preferences';
@@ -48,6 +49,7 @@ function mapStateToProps() {
const mapDispatchToProps = {
closePermalink,
getPostThread,
handleSelectChannelByName,
refreshChannelWithRetry,
setDeepLinkURL,

View File

@@ -95,7 +95,7 @@ const Header = ({
testID='post_header'
/>
)}
{!isSystemPost &&
{(!isSystemPost || isAutoResponse) &&
<HeaderTag
isAutoResponder={isAutoResponse}
isAutomation={isWebHook || isBot}

View File

@@ -15,6 +15,7 @@ import {isPostFlagged, isSystemMessage} from '@mm-redux/utils/post_utils';
import {canDeletePost} from '@selectors/permissions';
import {areConsecutivePosts, postUserDisplayName} from '@utils/post';
import type {StyleProp, ViewStyle} from 'react-native';
import type {Post as PostType} from '@mm-redux/types/posts';
import type {Theme} from '@mm-redux/types/preferences';
import type {GlobalState} from '@mm-redux/types/store';
@@ -27,6 +28,7 @@ type OwnProps = {
post?: PostType;
previousPostId?: string;
nextPostId?: string;
style?: StyleProp<ViewStyle>;
testID: string;
theme: Theme;
}

View File

@@ -8,6 +8,7 @@ import {injectIntl, intlShape} from 'react-intl';
import {showModalOverCurrentContext} from '@actions/navigation';
import SystemHeader from '@components/post_list/system_header';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import SystemAvatar from '@components/post_list/system_avatar';
import * as Screens from '@constants/screen';
import {Posts} from '@mm-redux/constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
@@ -43,6 +44,7 @@ type PostProps = {
showPermalink: (intl: typeof intlShape, teamName: string, postId: string) => null;
skipFlaggedHeader?: boolean;
skipPinnedHeader?: boolean;
style?: StyleProp<ViewStyle>;
teammateNameDisplay: string;
testID?: string;
theme: Theme
@@ -91,10 +93,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
const Post = ({
canDelete, enablePostUsernameOverride, highlight, highlightPinnedOrFlagged = true, intl, isConsecutivePost, isFirstReply, isFlagged, isLastReply,
location, post, removePost, rootPostAuthor, shouldRenderReplyButton, skipFlaggedHeader, skipPinnedHeader, showAddReaction = true, showPermalink,
teammateNameDisplay, testID, theme,
teammateNameDisplay, testID, theme, style,
}: PostProps) => {
const pressDetected = useRef(false);
const style = getStyleSheet(theme);
const styles = getStyleSheet(theme);
const handlePress = preventDoubleTap(() => {
pressDetected.current = true;
@@ -158,24 +160,27 @@ const Post = ({
const highlightFlagged = isFlagged && !skipFlaggedHeader;
const hightlightPinned = post.is_pinned && !skipPinnedHeader;
const itemTestID = `${testID}.${post.id}`;
const rightColumnStyle = [style.rightColumn, (post.root_id && isLastReply && style.rightColumnPadding)];
const pendingPostStyle: StyleProp<ViewStyle> | undefined = isPostPendingOrFailed(post) ? style.pendingPost : undefined;
const rightColumnStyle = [styles.rightColumn, (post.root_id && isLastReply && styles.rightColumnPadding)];
const pendingPostStyle: StyleProp<ViewStyle> | undefined = isPostPendingOrFailed(post) ? styles.pendingPost : undefined;
const isAutoResponder = fromAutoResponder(post);
let highlightedStyle: StyleProp<ViewStyle>;
if (highlight) {
highlightedStyle = style.highlight;
highlightedStyle = styles.highlight;
} else if ((highlightFlagged || hightlightPinned) && highlightPinnedOrFlagged) {
highlightedStyle = style.highlightPinnedOrFlagged;
highlightedStyle = styles.highlightPinnedOrFlagged;
}
let header: ReactNode;
let postAvatar: ReactNode;
let consecutiveStyle: StyleProp<ViewStyle>;
if (isConsecutivePost) {
consecutiveStyle = style.consective;
postAvatar = <View style={style.consecutivePostContainer}/>;
consecutiveStyle = styles.consective;
postAvatar = <View style={styles.consecutivePostContainer}/>;
} else {
postAvatar = (
postAvatar = isAutoResponder ? (
<SystemAvatar theme={theme}/>
) : (
<Avatar
pendingPostStyle={pendingPostStyle}
post={post}
@@ -183,7 +188,7 @@ const Post = ({
/>
);
if (isSystemMessage(post)) {
if (isSystemMessage(post) && !isAutoResponder) {
header = (
<SystemHeader
createAt={post.create_at}
@@ -206,7 +211,7 @@ const Post = ({
}
let body;
if (isSystemMessage(post) && !isPostEphemeral(post)) {
if (isSystemMessage(post) && !isPostEphemeral(post) && !isAutoResponder) {
body = (
<SystemMessage
post={post}
@@ -231,7 +236,7 @@ const Post = ({
return (
<View
testID={testID}
style={[style.postStyle, highlightedStyle]}
style={[styles.postStyle, style, highlightedStyle]}
>
<TouchableWithFeedback
testID={itemTestID}
@@ -250,7 +255,7 @@ const Post = ({
skipPinnedHeader={skipPinnedHeader}
theme={theme}
/>
<View style={[style.container, consecutiveStyle]}>
<View style={[styles.container, consecutiveStyle]}>
{postAvatar}
<View style={rightColumnStyle}>
{header}

View File

@@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ReactElement, useCallback, useEffect, useLayoutEffect, useRef} from 'react';
import React, {ReactElement, useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react';
import {injectIntl, intlShape} from 'react-intl';
import {DeviceEventEmitter, FlatList, Platform, RefreshControl, StyleSheet, ViewToken} from 'react-native';
import {DeviceEventEmitter, FlatList, NativeScrollEvent, NativeSyntheticEvent, Platform, StyleSheet, ViewToken} from 'react-native';
import {DeepLinkTypes, NavigationTypes} from '@constants';
import * as Screens from '@constants/screen';
@@ -27,6 +27,7 @@ import DateSeparator from './date_separator';
import MoreMessagesButton from './more_messages_button';
import NewMessagesLine from './new_message_line';
import Post from './post';
import PostListRefreshControl from './post_list_refresh_control';
type PostListProps = {
channelId?: string;
@@ -34,6 +35,7 @@ type PostListProps = {
currentTeamName: string;
deepLinkURL?: string;
extraData: never;
getPostThread: (rootId: string) => Promise<ActionResult>;
handleSelectChannelByName: (channelName: string, teamName: string, errorHandler: (intl: typeof intlShape) => void, intl: typeof intlShape) => Promise<ActionResult>;
highlightPinnedOrFlagged?: boolean;
highlightPostId?: string;
@@ -43,7 +45,9 @@ type PostListProps = {
location: string;
onLoadMoreUp: () => void;
postIds: string[];
refreshChannelWithRetry: (channelId: string) => Promise<ActionResult>;
renderFooter: () => ReactElement | null;
rootId?: string;
scrollViewNativeID?: string;
serverURL: string;
shouldRenderReplyButton?: boolean;
@@ -76,13 +80,21 @@ const styles = StyleSheet.create({
postListContent: {
paddingTop: 5,
},
scale: {
...Platform.select({
android: {
scaleY: -1,
},
}),
},
});
const buildExtraData = makeExtraData();
const PostList = ({
channelId, currentTeamName = '', closePermalink, deepLinkURL, extraData, handleSelectChannelByName, highlightPostId, highlightPinnedOrFlagged, initialIndex, intl,
loadMorePostsVisible, location, onLoadMoreUp = emptyFunction, postIds = [], renderFooter = (() => null),
channelId, currentTeamName = '', closePermalink, deepLinkURL, extraData, getPostThread,
handleSelectChannelByName, highlightPostId, highlightPinnedOrFlagged, initialIndex, intl, loadMorePostsVisible,
location, onLoadMoreUp = emptyFunction, postIds = [], refreshChannelWithRetry, renderFooter = (() => null), rootId,
serverURL = '', setDeepLinkURL, showMoreMessagesButton, showPermalink, siteURL = '', scrollViewNativeID, shouldRenderReplyButton, testID, theme,
}: PostListProps) => {
const prevChannelId = useRef(channelId);
@@ -90,6 +102,8 @@ const PostList = ({
const flatListRef = useRef<FlatList<never>>(null);
const onScrollEndIndexListener = useRef<onScrollEndIndexListenerEvent>();
const onViewableItemsChangedListener = useRef<ViewableItemsChangedListenerEvent>();
const [refreshing, setRefreshing] = useState(false);
const [offsetY, setOffsetY] = useState(0);
const registerViewableItemsListener = useCallback((listener) => {
onViewableItemsChangedListener.current = listener;
@@ -118,6 +132,18 @@ const PostList = ({
showPermalink(intl, teamName, postId);
};
const onRefresh = async () => {
if (location === Screens.CHANNEL && channelId) {
setRefreshing(true);
await refreshChannelWithRetry(channelId);
setRefreshing(false);
} else if (location === Screens.THREAD && rootId) {
setRefreshing(true);
await getPostThread(rootId);
setRefreshing(false);
}
};
const onScrollToIndexFailed = useCallback((info: ScrollIndexFailed) => {
const animationFrameIndexFailed = requestAnimationFrame(() => {
const index = Math.min(info.highestMeasuredFrameIndex, info.index);
@@ -164,6 +190,7 @@ const PostList = ({
theme={theme}
moreMessages={moreNewMessages && checkForPostId}
testID={`${testID}.new_messages_line`}
style={styles.scale}
/>
);
} else if (isDateLine(item)) {
@@ -171,6 +198,7 @@ const PostList = ({
<DateSeparator
date={getDateForDateLine(item)}
theme={theme}
style={styles.scale}
/>
);
}
@@ -178,6 +206,7 @@ const PostList = ({
if (isCombinedUserActivityPost(item)) {
const postProps = {
postId: item,
style: styles.scale,
testID: `${testID}.combined_user_activity`,
theme,
};
@@ -215,6 +244,7 @@ const PostList = ({
<Post
highlight={highlightPostId === item}
postId={item}
style={styles.scale}
testID={`${testID}.post`}
{...postProps}
/>
@@ -230,6 +260,17 @@ const PostList = ({
});
}, []);
const onScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
if (Platform.OS === 'android') {
const {y} = event.nativeEvent.contentOffset;
if (y === 0) {
setOffsetY(y);
} else if (offsetY === 0 && y !== 0) {
setOffsetY(y);
}
}
}, [offsetY]);
useResetNativeScrollView(scrollViewNativeID, postIds);
useEffect(() => {
@@ -283,49 +324,47 @@ const PostList = ({
}
}, [initialIndex, highlightPostId]);
let refreshControl;
if (location === Screens.PERMALINK) {
// Hack so that the scrolling the permalink does not dismisses the modal screens
refreshControl = (
<RefreshControl
refreshing={false}
enabled={Platform.OS === 'ios'}
colors={[theme.centerChannelColor]}
tintColor={theme.centerChannelColor}
/>);
}
const list = (
<FlatList
contentContainerStyle={styles.postListContent}
data={postIds}
extraData={buildExtraData(channelId, highlightPostId, extraData, loadMorePostsVisible)}
initialNumToRender={INITIAL_BATCH_TO_RENDER}
key={`recyclerFor-${channelId}-${hasPostsKey}`}
keyboardDismissMode={'interactive'}
keyboardShouldPersistTaps={'handled'}
keyExtractor={keyExtractor}
ListFooterComponent={renderFooter}
listKey={`recyclerFor-${channelId}`}
maintainVisibleContentPosition={SCROLL_POSITION_CONFIG}
maxToRenderPerBatch={Platform.select({android: 5})}
nativeID={scrollViewNativeID}
onEndReached={onLoadMoreUp}
onEndReachedThreshold={2}
onScroll={onScroll}
onScrollToIndexFailed={onScrollToIndexFailed}
onViewableItemsChanged={onViewableItemsChanged}
ref={flatListRef}
removeClippedSubviews={true}
renderItem={renderItem}
scrollEventThrottle={60}
style={styles.flex}
windowSize={Posts.POST_CHUNK_SIZE / 2}
viewabilityConfig={VIEWABILITY_CONFIG}
testID={testID}
/>
);
return (
<>
<FlatList
contentContainerStyle={styles.postListContent}
data={postIds}
extraData={buildExtraData(channelId, highlightPostId, extraData, loadMorePostsVisible)}
initialNumToRender={INITIAL_BATCH_TO_RENDER}
inverted={true}
key={`recyclerFor-${channelId}-${hasPostsKey}`}
keyboardDismissMode={'interactive'}
keyboardShouldPersistTaps={'handled'}
keyExtractor={keyExtractor}
ListFooterComponent={renderFooter}
listKey={`recyclerFor-${channelId}`}
maintainVisibleContentPosition={SCROLL_POSITION_CONFIG}
maxToRenderPerBatch={Platform.select({android: 5})}
nativeID={scrollViewNativeID}
onScrollToIndexFailed={onScrollToIndexFailed}
ref={flatListRef}
onEndReached={onLoadMoreUp}
onEndReachedThreshold={2}
removeClippedSubviews={true}
renderItem={renderItem}
scrollEventThrottle={60}
style={styles.flex}
windowSize={Posts.POST_CHUNK_SIZE / 2}
viewabilityConfig={VIEWABILITY_CONFIG}
onViewableItemsChanged={onViewableItemsChanged}
refreshControl={refreshControl}
testID={testID}
/>
<PostListRefreshControl
enabled={offsetY === 0}
refreshing={refreshing}
onRefresh={onRefresh}
theme={theme}
>
{list}
</PostListRefreshControl>
{showMoreMessagesButton &&
<MoreMessagesButton
channelId={channelId}

View File

@@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ReactElement} from 'react';
import {Platform, RefreshControl, StyleSheet} from 'react-native';
import type {Theme} from '@mm-redux/types/preferences';
type Props = {
children: ReactElement;
enabled: boolean;
onRefresh: () => void;
refreshing: boolean;
theme: Theme;
}
const style = StyleSheet.create({
container: {
flex: 1,
scaleY: -1,
},
});
const PostListRefreshControl = ({children, enabled, onRefresh, refreshing, theme}: Props) => {
const props = {
colors: [theme.onlineIndicator, theme.awayIndicator, theme.dndIndicator],
onRefresh,
refreshing,
tintColor: theme.centerChannelColor,
};
if (Platform.OS === 'android') {
return (
<RefreshControl
{...props}
enabled={enabled}
style={style.container}
>
{children}
</RefreshControl>
);
}
const refreshControl = <RefreshControl {...props}/>;
return React.cloneElement(
children,
{refreshControl, inverted: true},
);
};
export default PostListRefreshControl;

View File

@@ -118,13 +118,13 @@ const ProfilePicture = (props: ProfilePictureProps) => {
}, [props.profileImageUri]);
useDidUpdate(() => {
const {user} = props;
const {edit, user} = props;
const url = user ? Client4.getProfilePictureUrl(user.id, user.last_picture_update) : undefined;
if (url !== pictureUrl) {
if (url !== pictureUrl && !edit) {
setPictureUrl(url);
}
}, [props.user]);
}, [props.user, props.edit]);
let statusIcon;
let statusStyle = props.statusStyle;

View File

@@ -381,7 +381,7 @@ class FilteredList extends Component {
<View style={styles.container}>
<SectionList
sections={dataSource}
removeClippedSubviews={true}
removeClippedSubviews={false}
renderItem={this.renderItem}
renderSectionHeader={this.renderSectionHeader}
keyExtractor={this.keyExtractor}

View File

@@ -18,6 +18,7 @@ import {resetMomentLocale} from '@i18n';
import {setupPermanentSidebar} from '@init/device';
import PushNotifications from '@init/push_notifications';
import {setAppState, setServerVersion} from '@mm-redux/actions/general';
import {getTeams} from '@mm-redux/actions/teams';
import {autoUpdateTimezone} from '@mm-redux/actions/timezone';
import {close as closeWebSocket} from '@actions/websocket';
import {Client4} from '@client/rest';
@@ -213,6 +214,7 @@ class GlobalEventHandler {
await dispatch(loadConfigAndLicense());
await dispatch(loadMe(user));
dispatch(getTeams());
const window = Dimensions.get('window');
this.onOrientationChange({window});

View File

@@ -75,8 +75,11 @@ describe('PushNotification', () => {
const deliveredNotifications = [{identifier: 1}, {identifier: 2}];
Notifications.setDeliveredNotifications(deliveredNotifications);
await PushNotification.clearChannelNotifications();
await PushNotification.clearChannelNotifications('some other channel');
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(deliveredNotifications.length);
await PushNotification.clearChannelNotifications();
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(0);
});
it('clearChannelNotifications should set app badge number from redux store when set', async () => {
@@ -90,7 +93,7 @@ describe('PushNotification', () => {
const stateBadgeCount = 2 * deliveredNotifications.length;
ViewSelectors.getBadgeCount = jest.fn().mockReturnValue(stateBadgeCount);
await PushNotification.clearChannelNotifications();
await PushNotification.clearChannelNotifications('some other channel');
expect(setApplicationIconBadgeNumber).toHaveBeenCalledWith(stateBadgeCount);
});
@@ -103,7 +106,7 @@ describe('PushNotification', () => {
deliveredNotifications = [];
Notifications.setDeliveredNotifications(deliveredNotifications);
await PushNotification.clearChannelNotifications();
await PushNotification.clearChannelNotifications('some other channel');
expect(setBadgeCount).toHaveBeenCalledWith(0);
});
});

View File

@@ -88,6 +88,18 @@ class PushNotifications {
//set the badge count to the total amount of notifications present in the not-center
let badgeCount = notifications.length;
for (let i = 0; i < notifications.length; i++) {
const notification = notifications[i] as NotificationWithChannel;
if (notification.channel_id === channelId) {
ids.push(notification.identifier);
badgeCount--;
}
}
if (ids.length) {
Notifications.ios.removeDeliveredNotifications(ids);
}
if (Store.redux) {
const totalMentions = getBadgeCount(Store.redux.getState());
if (totalMentions > -1) {
@@ -96,17 +108,6 @@ class PushNotifications {
}
}
for (let i = 0; i < notifications.length; i++) {
const notification = notifications[i] as NotificationWithChannel;
if (notification.channel_id === channelId) {
ids.push(notification.identifier);
}
}
if (ids.length) {
Notifications.ios.removeDeliveredNotifications(ids);
}
if (Platform.OS === 'ios') {
badgeCount = badgeCount <= 0 ? 0 : badgeCount;
Notifications.ios.setBadgeCount(badgeCount);

View File

@@ -22,6 +22,8 @@ const Preferences: Dictionary<any> = {
INTERVAL_FIFTEEN_MINUTES: 15 * 60,
INTERVAL_HOUR: 60 * 60,
INTERVAL_IMMEDIATE: 30,
CATEGORY_EMOJI: 'emoji',
EMOJI_SKINTONE: 'emoji_skintone',
CATEGORY_CUSTOM_STATUS: 'custom_status',
NAME_CUSTOM_STATUS_TUTORIAL_STATE: 'custom_status_tutorial_state',

View File

@@ -23,7 +23,6 @@ export default class ChannelPostList extends PureComponent {
getPostThread: PropTypes.func.isRequired,
increasePostVisibility: PropTypes.func.isRequired,
selectPost: PropTypes.func.isRequired,
refreshChannelWithRetry: PropTypes.func.isRequired,
setChannelRefreshing: PropTypes.func,
}).isRequired,
channelId: PropTypes.string.isRequired,

View File

@@ -15,7 +15,6 @@ describe('ChannelPostList', () => {
getPostThread: jest.fn(),
increasePostVisibility: jest.fn(),
selectPost: jest.fn(),
refreshChannelWithRetry: jest.fn(),
},
channelId: 'channel-id',
loadMorePostsVisible: false,

View File

@@ -4,11 +4,7 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {
loadPostsIfNecessaryWithRetry,
increasePostVisibility,
refreshChannelWithRetry,
} from '@actions/views/channel';
import {loadPostsIfNecessaryWithRetry, increasePostVisibility} from '@actions/views/channel';
import {getPostThread} from '@actions/views/post';
import {Types} from '@constants';
import {selectPost} from '@mm-redux/actions/posts';
@@ -45,7 +41,6 @@ function mapDispatchToProps(dispatch) {
getPostThread,
increasePostVisibility,
selectPost,
refreshChannelWithRetry,
}, dispatch),
};
}

View File

@@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {getConfig} from '@mm-redux/selectors/entities/general';
import {getTheme} from '@mm-redux/selectors/entities/preferences';
import {getTeammateNameDisplaySetting, getTheme} from '@mm-redux/selectors/entities/preferences';
import {getCurrentUser} from '@mm-redux/selectors/entities/users';
import {updateMe} from '@mm-redux/actions/users';
@@ -19,6 +19,7 @@ function mapStateToProps(state) {
return {
config,
teammateNameDisplay: getTeammateNameDisplaySetting(state),
theme,
updateMeRequest,
currentUser,

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable max-lines */
import React from 'react';
import {injectIntl} from 'react-intl';
import {
@@ -14,13 +16,14 @@ import {
} from 'react-native';
import deepEqual from 'deep-equal';
import PropTypes from 'prop-types';
import PropTypes, {string} from 'prop-types';
import FormattedText from '@components/formatted_text';
import RadioButtonGroup from '@components/radio_button';
import StatusBar from '@components/status_bar';
import PushNotifications from '@init/push_notifications';
import {RequestStatus} from '@mm-redux/constants';
import {displayUsername} from '@mm-redux/utils/user_utils';
import SectionItem from '@screens/settings/section_item';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {getNotificationProps} from '@utils/notify_props';
@@ -31,6 +34,7 @@ import NotificationSettingsMobileBase from './notification_settings_mobile_base'
class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase {
static propTypes = {
...NotificationSettingsMobileBase.propTypes,
teammateNameDisplay: string,
updateMeRequest: PropTypes.object.isRequired,
}
@@ -694,13 +698,15 @@ class NotificationSettingsMobileAndroid extends NotificationSettingsMobileBase {
};
sendTestNotification = () => {
const {intl} = this.props;
const {currentUser, intl, teammateNameDisplay} = this.props;
PushNotifications.localNotification({
body: intl.formatMessage({
id: 'mobile.notification_settings_mobile.test_push',
defaultMessage: 'This is a test push notification',
}),
sender_name: displayUsername(currentUser, teammateNameDisplay),
sender_id: currentUser.id,
userInfo: {
local: true,
test: true,

View File

@@ -44,6 +44,7 @@ exports[`thread should match snapshot, has root post 1`] = `
style={Object {}}
/>
}
rootId="root_id"
scrollViewNativeID="threadPostList"
testID="thread.post_list"
/>
@@ -134,6 +135,7 @@ exports[`thread should match snapshot, render footer 1`] = `
style={Object {}}
/>
}
rootId="root_id"
scrollViewNativeID="threadPostList"
testID="thread.post_list"
/>
@@ -153,6 +155,7 @@ exports[`thread should match snapshot, render footer 2`] = `
]
}
renderFooter={null}
rootId="root_id"
scrollViewNativeID="threadPostList"
testID="thread.post_list"
/>

View File

@@ -42,6 +42,7 @@ export default class ThreadAndroid extends ThreadBase {
currentUserId={myMember && myMember.user_id}
lastViewedAt={this.state.lastViewedAt}
location={THREAD}
rootId={rootId}
/>
</Animated.View>
<PostDraft

View File

@@ -55,6 +55,7 @@ export default class ThreadIOS extends ThreadBase {
currentUserId={myMember && myMember.user_id}
lastViewedAt={this.state.lastViewedAt}
location={THREAD}
rootId={rootId}
scrollViewNativeID={SCROLLVIEW_NATIVE_ID}
/>
</Animated.View>

View File

@@ -6,6 +6,8 @@ import {createSelector} from 'reselect';
import {getCustomEmojisByName as selectCustomEmojisByName} from '@mm-redux/selectors/entities/emojis';
import {createIdsSelector} from '@mm-redux/utils/helpers';
import {CategoryNames, CategoryTranslations, Emojis, EmojiIndicesByAlias, EmojiIndicesByCategory, CategoryMessage} from '@utils/emojis';
import {get} from '@mm-redux/selectors/entities/preferences';
import {Preferences} from '@mm-redux/constants';
const icons = {
recent: 'clock-outline',
@@ -38,24 +40,47 @@ function fillEmoji(indice) {
};
}
// if an emoji
// - has `skin_variations` then it uses the default skin (yellow)
// - has `skins` it's first value is considered the skin version (it can contain more values)
// - any other case it doesn't have variations or is a custom emoji.
function getSkin(emoji) {
if ('skin_variations' in emoji) {
return 'default';
}
if ('skins' in emoji) {
return emoji.skins && emoji.skins[0];
}
return null;
}
export const selectEmojisByName = createIdsSelector(
selectCustomEmojisByName,
(customEmojis) => {
getUserSkinTone,
(customEmojis, skinTone) => {
const emoticons = new Set();
for (const [key] of [...EmojiIndicesByAlias.entries(), ...customEmojis.entries()]) {
if (!key.includes('skin_tone')) {
for (const [key, index] of EmojiIndicesByAlias.entries()) {
const skin = getSkin(Emojis[index]);
if (!skin || skin === skinTone) {
emoticons.add(key);
}
}
for (const [key] of customEmojis.entries()) {
emoticons.add(key);
}
return Array.from(emoticons);
},
);
export function getUserSkinTone(state) {
return get(state, Preferences.CATEGORY_EMOJI, Preferences.EMOJI_SKINTONE, 'default');
}
export const selectEmojisBySection = createSelector(
selectCustomEmojisByName,
(state) => state.views.recentEmojis,
(customEmojis, recentEmojis) => {
getUserSkinTone,
(customEmojis, recentEmojis, skinTone) => {
const customEmojiItems = [];
for (const [key] of customEmojis) {
customEmojiItems.push({
@@ -66,8 +91,7 @@ export const selectEmojisBySection = createSelector(
const filteredCategories = CategoryNames.filter((category) => category !== 'recent' || recentItems.length > 0);
const emoticons = filteredCategories.map((category) => {
// TODO: change default into user selected category once the user is able to choose it
const items = EmojiIndicesByCategory.get('default').get(category).map(fillEmoji);
const items = EmojiIndicesByCategory.get(skinTone).get(category).map(fillEmoji);
const data = items;
if (category === 'custom') {
data.push(...customEmojiItems);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -141,6 +141,24 @@
"date_separator.yesterday": "Вчера",
"edit_post.editPost": "Редактиране на публикация...",
"edit_post.save": "Запиши",
"emoji_picker.activities": "Дейности",
"emoji_picker.animals-nature": "Животни и природа",
"emoji_picker.custom": "Потребителски",
"emoji_picker.flags": "Флагове",
"emoji_picker.food-drink": "Храни и напитки",
"emoji_picker.objects": "Обекти",
"emoji_picker.people-body": "Хора и тяло",
"emoji_picker.recent": "Последни",
"emoji_picker.searchResults": "Резултати от търсенето",
"emoji_picker.smileys-emotion": "Усмивки и емоции",
"emoji_picker.symbols": "Символи",
"emoji_picker.travel-places": "Пътуване и места",
"emoji_skin.dark_skin_tone": "Тъмна кожа",
"emoji_skin.default": "Цвят на кожата по подразбиране",
"emoji_skin.light_skin_tone": "Светла кожа",
"emoji_skin.medium_dark_skin_tone": "Средно тъмна кожа",
"emoji_skin.medium_light_skin_tone": "Средно светла кожа",
"emoji_skin.medium_skin_tone": "Среден цвят на кожата",
"file_upload.fileAbove": "Файла трябва да бъде по-малък от {max}",
"gallery.download_file": "Свали файла",
"gallery.footer.channel_name": "Споделено в {channelName}",
@@ -646,7 +664,7 @@
"suggestion.mention.morechannels": "Други канали",
"suggestion.mention.nonmembers": "Не е в канала",
"suggestion.mention.special": "Специални споменавания",
"suggestion.mention.you": "(Вие)",
"suggestion.mention.you": " (Вие)",
"suggestion.search.direct": "Директно съобщение",
"suggestion.search.private": "Частни канали",
"suggestion.search.public": "Публични канали",

View File

@@ -46,7 +46,7 @@
"apps.error.responses.unexpected_error": "Unvorhergesehenen Fehler erhalten.",
"apps.error.responses.unexpected_type": "Antworttyp App war nicht erwartet. Antworttyp: {type}.",
"apps.error.responses.unknown_type": "Der Typ der Antwort der App wird nicht unterstützt. Typ der Antwort: {type}.",
"apps.error.unknown": "Es gab einen unbekannten Fehler.",
"apps.error.unknown": "Ein unbekannter Fehler ist aufgetreten.",
"apps.suggestion.dynamic.error": "Dynamischer Auswahl Fehler",
"apps.suggestion.errors.parser_error": "Fehler beim Parsen",
"apps.suggestion.no_dynamic": "Keine Daten bei dynamischen Vorschlägen geliefert",
@@ -268,6 +268,7 @@
"mobile.channel_list.members": "MITGLIEDER",
"mobile.channel_list.not_member": "KEIN MITGLIED",
"mobile.channel_list.unreads": "UNGELESENE",
"mobile.channel_loader.still_loading": "Versuche weiterhin Inhalte zu laden...",
"mobile.channel_members.add_members_alert": "Sie müssen mindestens ein Mitglied auswählen, um es dem Kanal hinzuzufügen.",
"mobile.commands.error_title": "Fehler beim Ausführen des Befehls",
"mobile.components.error_list.dismiss_all": "Alle verwerfen",
@@ -281,6 +282,8 @@
"mobile.create_channel.public": "Neuer Öffentlicher Kanal",
"mobile.create_post.read_only": "Dieser Kanal ist schreibgeschützt",
"mobile.custom_list.no_results": "Keine Ergebnisse",
"mobile.custom_status.choose_emoji": "Wähle einen Emoji",
"mobile.custom_status.modal_confirm": "Erledigt",
"mobile.display_settings.sidebar": "Seitenleiste",
"mobile.display_settings.theme": "Motiv",
"mobile.document_preview.failed_description": "Es ist ein Fehler beim Öffnen des Dokuments aufgetreten. Bitte stellen Sie sicher, dass Sie einen Betrachter für {fileType} installiert haben und versuchen es erneut.\n",
@@ -314,6 +317,8 @@
"mobile.file_upload.browse": "Dateien durchsuchen",
"mobile.file_upload.camera_photo": "Foto aufnehmen",
"mobile.file_upload.camera_video": "Video aufnehmen",
"mobile.file_upload.disabled": "Hochladen von Dateien über die mobile App ist deaktiviert. Bitte den System Admin für Details kontaktieren.",
"mobile.file_upload.disabled2": "Hochladen von Dateien über die mobile App ist deaktiviert.",
"mobile.file_upload.library": "Foto-Bibliothek",
"mobile.file_upload.max_warning": "Uploads sind auf maximal fünf Dateien beschränkt.",
"mobile.file_upload.unsupportedMimeType": "Nur BMP-, JPG- oder PNG-Bilder sind als Profilbilder zugelassen.",
@@ -321,8 +326,9 @@
"mobile.files_paste.error_description": "Fehler beim Einfügen der Datei(en). Bitte erneut versuchen.",
"mobile.files_paste.error_dismiss": "Verwerfen",
"mobile.files_paste.error_title": "Einfügen fehlgeschlagen",
"mobile.flagged_posts.empty_description": "Markierungen dienen als Möglichkeit, Nachrichten für eine Wiedervorlage zu markieren. Ihre Markierungen sind persönlich und können nicht von anderen Benutzern gesehen werden.",
"mobile.flagged_posts.empty_title": "Keine markierte Nachrichten",
"mobile.flagged_posts.empty_description": "Gespeicherte Nachrichten sind nur für Dich sichtbar. Markiere Nachrichten zur Nachverfolgung oder speichere sie für später, indem länger auf die Nachricht gedrückt wird und dann Speichern ausgewählt wird.",
"mobile.flagged_posts.empty_title": "Keine gespeicherten Nachrichten",
"mobile.gallery.title": "{index} von {total}",
"mobile.general.error.title": "Fehler",
"mobile.help.title": "Hilfe",
"mobile.intro_messages.DM": "Dies ist der Start der Privatnachrichtenverlaufs mit {teammate}. Privatnachrichten und hier geteilte Dateien sind für Personen außerhalb dieses Bereichs nicht sichtbar.",
@@ -331,12 +337,14 @@
"mobile.ios.photos_permission_denied_description": "Laden Sie Fotos und Videos auf ihre Mattermost-Instanz hoch oder speichern Sie sie auf Ihrem Gerät. Öffnen Sie die Einstellungen, um Mattermost Lese- und Schreibzugriff auf ihre Foto- und Videobibliothek zu gewähren.",
"mobile.ios.photos_permission_denied_title": "{applicationName} möchte auf Ihre Fotos zugreifen",
"mobile.join_channel.error": "Dem Kanal {displayName} konnte nicht beigetreten werden. Bitte überprüfen Sie ihre Verbindung und versuchen es erneut.",
"mobile.link.error.text": "Link kann nicht geöffnet werden.",
"mobile.link.error.title": "Fehler",
"mobile.loading_channels": "Lade Kanäle...",
"mobile.loading_members": "Lade Mitglieder...",
"mobile.loading_options": "Lade Optionen...",
"mobile.loading_posts": "Lade Nachrichten...",
"mobile.login_options.choose_title": "Wählen Sie ihre Anmeldemethode",
"mobile.mailTo.error.text": "Email App kann nicht geöffnet werden.",
"mobile.mailTo.error.title": "Fehler",
"mobile.managed.blocked_by": "Blockiert durch {vendor}",
"mobile.managed.exit": "Beenden",
@@ -360,6 +368,7 @@
"mobile.more_dms.start": "Start",
"mobile.more_dms.title": "Neue Konversation",
"mobile.more_dms.you": "@{username} - Sie",
"mobile.more_messages_button.text": "{count} {isInitialMessage, select, true {new} other {more new}} {count, plural, one {message} other {messages}}",
"mobile.notice_mobile_link": "mobile Apps",
"mobile.notice_platform_link": "Server",
"mobile.notice_text": "Mattermost wird durch die in {platform} und {mobile} verwendete Open-Source-Software ermöglicht.",
@@ -382,7 +391,7 @@
"mobile.notification_settings.modal_cancel": "ABBRECHEN",
"mobile.notification_settings.modal_save": "SPEICHERN",
"mobile.notification_settings.ooo_auto_responder": "Automatische Antworten auf Direktnachrichten",
"mobile.notification_settings.save_failed_description": "Die Benachrichtigungseinstellungen konnten durch ein Verbindungsproblem nicht gespeichert werden, bitte erneut versuchen.",
"mobile.notification_settings.save_failed_description": "Die Benachrichtigungseinstellungen konnten durch ein Verbindungsproblem nicht gespeichert werden. Bitte erneut versuchen.",
"mobile.notification_settings.save_failed_title": "Verbindungsproblem",
"mobile.notification_settings_mentions.keywords": "Stichwörter",
"mobile.notification_settings_mentions.keywordsDescription": "Andere Wörter, die eine Erwähnung auslösen",
@@ -400,8 +409,13 @@
"mobile.notification_settings_mobile.test": "Schicke mir eine Testbenachrichtigung",
"mobile.notification_settings_mobile.test_push": "Dies ist eine Test-Push-Benachrichtigung",
"mobile.notification_settings_mobile.vibrate": "Vibrieren",
"mobile.oauth.failed_to_login": "Ihre Anmeldeversuch ist fehlgeschlagen. Bitte nochmal versuchen.",
"mobile.oauth.failed_to_open_link": "Der Link konnte nicht geöffnet werden. Bitte nochmal versuchen.",
"mobile.oauth.failed_to_open_link_no_browser": "Der Link konnte nicht geöffnet werden. Bitte prüfen, ob ein Browser installiert ist.",
"mobile.oauth.restart_login": "Anmeldung neu starten",
"mobile.oauth.something_wrong": "Etwas ist schief gelaufen",
"mobile.oauth.something_wrong.okButon": "OK",
"mobile.oauth.switch_to_browser": "Bitte verwenden Sie ihren Browser um den Anmeldeprozess abzuschließen.",
"mobile.oauth.try_again": "Erneut versuchen",
"mobile.offlineIndicator.connected": "Verbunden",
"mobile.offlineIndicator.connecting": "Verbinde...",
@@ -411,7 +425,7 @@
"mobile.open_unknown_channel.error": "Konnte Kanal nicht beitreten. Bitte setzen Sie den Cache zurück und versuchen es erneut.",
"mobile.permission_denied_dismiss": "Nicht erlauben",
"mobile.permission_denied_retry": "Einstellungen",
"mobile.pinned_posts.empty_description": "Wichtige Elemente anheften durch gedrückt halten einer Nachricht und wählen von \"An Kanal anheften\".",
"mobile.pinned_posts.empty_description": "Wichtige Nachrichten anheften, die für den ganzen Kanal sichtbar sind. Länger auf eine Nachricht drücken und \"An Kanal anheften\" auswählen.",
"mobile.pinned_posts.empty_title": "Keine angehefteten Nachrichten",
"mobile.post.cancel": "Abbrechen",
"mobile.post.delete_question": "Möchten Sie diese Nachricht wirklich löschen?",
@@ -422,31 +436,39 @@
"mobile.post.retry": "Aktualisieren",
"mobile.post_info.add_reaction": "Reaktion hinzufügen",
"mobile.post_info.copy_text": "Text kopieren",
"mobile.post_info.flag": "Markieren",
"mobile.post_info.flag": "Speichern",
"mobile.post_info.mark_unread": "Als ungelesen markieren",
"mobile.post_info.pin": "An Kanal anheften",
"mobile.post_info.reply": "Antworten",
"mobile.post_info.unflag": "Markierung entfernen",
"mobile.post_info.unflag": "Speicherung entfernen",
"mobile.post_info.unpin": "Vom Kanal abheften",
"mobile.post_pre_header.flagged": "Markiert",
"mobile.post_pre_header.flagged": "Gespeichert",
"mobile.post_pre_header.pinned": "Angeheftet",
"mobile.post_pre_header.pinned_flagged": "Angeheftet und markiert",
"mobile.post_pre_header.pinned_flagged": "Angeheftet und gespeichert",
"mobile.post_textbox.entire_channel.cancel": "Abbrechen",
"mobile.post_textbox.entire_channel.confirm": "Bestätigen",
"mobile.post_textbox.entire_channel.message": "Durch die Verwendung von @all oder @channel benachrichtigen Sie {totalMembers, number} {totalMembers, plural, one {Person} other {Personen}}. Sind Sie sicher, dass sie dies tun möchten?",
"mobile.post_textbox.entire_channel.message.with_timezones": "Durch die Verwendung von @all oder @channel benachrichtigen Sie {totalMembers, number} {totalMembers, plural, one {Person} other {Personen}} in {timezones, number} {timezones, plural, one {Zeitzone} other {Zeitzonen}}. Sind Sie sicher, dass sie dies tun möchten?",
"mobile.post_textbox.entire_channel.title": "Bestätigen Sie das Senden von Benachrichtigungen an den gesamten Kanal",
"mobile.post_textbox.groups.title": "Benachrichtigung an Gruppe senden bestätigen",
"mobile.post_textbox.multi_group.message.with_timezones": "Wenn Sie {mentions} und {finalMention} verwenden, werden Sie Benachrichtigungen an mindestens {totalMembers} Menschen in {timezones, number} {timezones, plural, one {timezone} other {timezones}}. Sind Sie sich sicher, dass Sie dies tun wollen?",
"mobile.post_textbox.multi_group.message.without_timezones": "Beim Verwenden von {mentions} und {finalMention} werden Benachrichtigungen an mindestens {totalMembers} Personen gesendet. Sind Sie sicher, dass Sie das möchten?",
"mobile.post_textbox.one_group.message.with_timezones": "Durch Verwendung von {mention} werden Nachrichten an {totalMembers} Personen in {timezones, number} {timezones, plural, one {timezone} other {timezones}} gesendet. Sind Sie sicher?",
"mobile.post_textbox.one_group.message.without_timezones": "Durch Verwendung von {mention} werden Benachrichtigungen an {totalMembers} Personen gesendet. Sind Sie sicher?",
"mobile.post_textbox.uploadFailedDesc": "Einige Anhänge konnten nicht auf den Server hochgeladen werden. Sind Sie sicher, dass sie die Nachricht abschicken wollen?",
"mobile.post_textbox.uploadFailedTitle": "Anhang Fehler",
"mobile.posts_view.moreMsg": "Weitere neue Nachrichten oberhalb",
"mobile.prepare_file.failed_description": "Beim Erstellen der Datei ist ein Fehler aufgetreten. Bitte nochmal versuchen.\n",
"mobile.prepare_file.failed_title": "Erstellen fehlgeschlagen",
"mobile.prepare_file.text": "Erstelle",
"mobile.privacy_link": "Datenschutzbedingungen",
"mobile.public_link.copied": "Öffentlicher Link kopiert",
"mobile.push_notification_reply.button": "Senden",
"mobile.push_notification_reply.placeholder": "Eine Antwort schreiben...",
"mobile.push_notification_reply.title": "Antworten",
"mobile.reaction_header.all_emojis": "Alle",
"mobile.recent_mentions.empty_description": "Hier werden Nachrichten auftauchen, die ihren Benutzernamen oder andere Wörter enthalten, die Erwähnungen auslösen.",
"mobile.recent_mentions.empty_title": "Keine letzten Erwähnungen",
"mobile.recent_mentions.empty_description": "Nachrichten, in denen jemand Sie erwähnt oder Wörter verwendet, die Erwähnungen auslösen, werden hier gespeichert.",
"mobile.recent_mentions.empty_title": "Bisher keine Erwähnungen",
"mobile.rename_channel.display_name_maxLength": "Kanalname muss kürzer als {maxLength, number} Zeichen sein",
"mobile.rename_channel.display_name_minLength": "Kanalname muss {minLength, number} oder mehr Zeichen enthalten",
"mobile.rename_channel.display_name_required": "Kanalname ist erforderlich",
@@ -458,7 +480,7 @@
"mobile.reset_status.alert_cancel": "Abbrechen",
"mobile.reset_status.alert_ok": "Ok",
"mobile.reset_status.title_ooo": "\"Nicht im Büro\" deaktivieren?",
"mobile.retry_message": "Aktualisierung der Nachrichten fehlgeschlagen. Nach oben ziehen, um erneut zu versuchen.",
"mobile.retry_message": "Aktualisierung der Nachrichten fehlgeschlagen. Nach drücken, um erneut zu versuchen.",
"mobile.routes.channelInfo": "Info",
"mobile.routes.channelInfo.createdBy": "Erstellt durch {creator} am ",
"mobile.routes.channelInfo.delete_channel": "Kanal archivieren",
@@ -470,6 +492,7 @@
"mobile.routes.channel_members.action_message_confirm": "Sind Sie sicher, dass Sie die ausgewählten Mitglieder vom Kanal löschen möchten?",
"mobile.routes.code": "{language}-Code",
"mobile.routes.code.noLanguage": "Code",
"mobile.routes.custom_status": "Setzte einen Status",
"mobile.routes.edit_profile": "Profil bearbeiten",
"mobile.routes.login": "Anmelden",
"mobile.routes.loginOptions": "Loginauswahl",
@@ -484,6 +507,7 @@
"mobile.routes.user_profile": "Profil",
"mobile.routes.user_profile.edit": "Bearbeiten",
"mobile.routes.user_profile.local_time": "LOKALE ZEIT",
"mobile.routes.user_profile.organization": "ORGANISATION",
"mobile.routes.user_profile.send_message": "Nachricht versenden",
"mobile.search.after_modifier_description": "um Nachrichten nach einem spezifischen Datum zu finden",
"mobile.search.before_modifier_description": "um Nachrichten vor einem spezifischen Datum zu finden",
@@ -502,6 +526,7 @@
"mobile.server_link.error.title": "Link-Fehler",
"mobile.server_link.unreachable_channel.error": "Der Link gehört zu einem gelöschten Kanal oder einem Kanal auf den Sie keinen Zugriff haben.",
"mobile.server_link.unreachable_team.error": "Der Link verweist auf ein gelöschtes Team oder ein Team auf das Sie keinen Zugriff haben.",
"mobile.server_link.unreachable_user.error": "Dies Link gehört zu einem gelöschten Benutzer.",
"mobile.server_ssl.error.text": "Das Sicherheitszertifikat von {host} ist ungültig.\n\nBitte kontaktieren Sie Ihren System Administrator um die Probleme mit dem Sicherheitszertifikat zu beheben und somit Verbindungen zum Server zu erlauben.",
"mobile.server_ssl.error.title": "Ungültiges Sicherheitszertifikat",
"mobile.server_upgrade.alert_description": "Diese Serverversion wird nicht mehr unterstützt und es können potentielle Kompatibilitätsprobleme für Nutzer auftreten, die zu Abstürzen oder schwerwiegenden Beeinträchtigungen der Hauptfunktionen der Applikation führen können. Eine Aktualisierung der Serverversion {serverVersion} oder aktueller ist nötig.",
@@ -509,6 +534,7 @@
"mobile.server_upgrade.dismiss": "Verwerfen",
"mobile.server_upgrade.learn_more": "Mehr erfahren",
"mobile.server_upgrade.title": "Serveraktualisierung erforderlich",
"mobile.server_url.empty": "Bitte eine gültige Server URL eingeben",
"mobile.server_url.invalid_format": "URL muss mit http:// oder https:// beginnen",
"mobile.session_expired": "Die Sitzung ist abgelaufen: Bitte melden Sie sich an, um weiterhin Benachrichtigungen zu erhalten. Sitzungen für {siteName} sind so konfiguriert, dass sie nach {daysCount, number} {daysCount, plural, one {Tag} other {Tagen}} ablaufen.",
"mobile.set_status.away": "Abwesend",
@@ -544,9 +570,13 @@
"mobile.timezone_settings.manual": "Zeitzone ändern",
"mobile.timezone_settings.select": "Zeitzone auswählen",
"mobile.tos_link": "Nutzungsbedingungen",
"mobile.unsupported_server.message": "Anhänge, Linkvorschauen, Reaktionen und eingebettete Daten werden eventuell nicht korrekt angezeigt. Wenn dieser Zustand anhält, den System Administrator bitten, den Mattermost Server zu aktualisieren.",
"mobile.unsupported_server.ok": "OK",
"mobile.unsupported_server.title": "Nicht unterstützte Serverversion",
"mobile.user.settings.notifications.email.fifteenMinutes": "Alle 15 Minuten",
"mobile.user_list.deactivated": "Deaktiviert",
"mobile.user_removed.message": "Sie wurden aus dem Kanal entfernt.",
"mobile.user_removed.title": "Entfernt aus {channelName}",
"mobile.video_playback.failed_description": "Es ist ein Fehler beim Abspielen des Videos aufgetreten.\n",
"mobile.video_playback.failed_title": "Videowiedergabe fehlgeschlagen",
"mobile.youtube_playback_error.description": "Es ist ein Fehler beim Versuch ein YouTube-Video abzuspielen aufgetreten.\nDetails: {details}",
@@ -560,14 +590,20 @@
"more_channels.dropdownTitle": "Anzeigen",
"more_channels.noMore": "Keine weiteren Kanäle, denen beigetreten werden kann",
"more_channels.publicChannels": "Öffentliche Kanäle",
"more_channels.sharedChannels": "Geteilte Kanäle",
"more_channels.showArchivedChannels": "Anzeigen: Archivierte Kanäle",
"more_channels.showPublicChannels": "Anzeigen: Öffentliche Kanäle",
"more_channels.showSharedChannels": "Anzeige: Geteilte Kanäle",
"more_channels.title": "Weitere Kanäle",
"msg_typing.areTyping": "{users} und {last} tippen gerade...",
"msg_typing.isTyping": "{user} tippt...",
"navbar.channel_drawer.button": "Kanäle und Teams",
"navbar.channel_drawer.hint": "Öffnet die Kanal- und Team-Schublade",
"navbar.leave": "Kanal verlassen",
"navbar.more_options.button": "Mehr Optionen",
"navbar.more_options.hint": "Öffnet die weiteren Optionen in der rechten Seitenleiste",
"navbar.search.button": "Kanalsuche",
"navbar.search.hint": "Öffnet die Kanalsuche",
"password_form.title": "Passwort zurücksetzen",
"password_send.checkInbox": "Bitte prüfen Sie den Posteingang.",
"password_send.description": "Um ihr Passwort zurückzusetzen, geben Sie die E-Mail-Adresse an, die Sie zur Registrierung verwendet haben",
@@ -577,6 +613,9 @@
"permalink.error.access": "Der dauerhafte Link verweist auf eine gelöschte Nachricht oder einen Kanal auf den Sie keinen Zugriff haben.",
"permalink.error.link_not_found": "Link nicht gefunden",
"permalink.show_dialog_warn.cancel": "Abbrechen",
"permalink.show_dialog_warn.description": "Sie sind dabei, \"{channel}\" beizutreten ohne explizit durch den Kanaladministrator hinzugefügt worden zu sein. Sind Sie sicher, dass Sie diesem privaten Kanal beitreten wollen?",
"permalink.show_dialog_warn.join": "Beitreten",
"permalink.show_dialog_warn.title": "Privatem Kanal beitreten",
"post_body.check_for_out_of_channel_groups_mentions.message": "wurde durch diese Erwähnung nicht benachrichtigt, da sich der Benutzer nicht im Kanal befindet. Er kann dem Kanal nicht hinzugefügt werden, da er nicht Mitglied der verknüpften Gruppen ist. Um ihn zu diesem Kanal hinzuzufügen, muss er zu den verknüpften Gruppen hinzugefügt werden.",
"post_body.check_for_out_of_channel_mentions.link.and": " und ",
"post_body.check_for_out_of_channel_mentions.link.private": "sie zu diesem privaten Kanal hinzufügen",
@@ -598,7 +637,7 @@
"search_bar.search": "Suche",
"search_header.results": "Suchergebnisse",
"search_header.title2": "Letzte Erwähnungen",
"search_header.title3": "Markierte Nachrichten",
"search_header.title3": "Gespeicherte Nachrichten",
"search_item.channelArchived": "Archiviert",
"sidebar.channels": "ÖFFENTLICHE KANÄLE",
"sidebar.direct": "DIREKTNACHRICHTEN",
@@ -619,13 +658,13 @@
"suggestion.mention.all": "Benachrichtigt jeden in diesem Kanal",
"suggestion.mention.channel": "Benachrichtigt jeden in diesem Kanal",
"suggestion.mention.channels": "Meine Kanäle",
"suggestion.mention.groups": "Group Mentions",
"suggestion.mention.groups": "Gruppenerwähnungen",
"suggestion.mention.here": "Benachrichtigt jeden in diesem Kanal",
"suggestion.mention.members": "Kanalmitglieder",
"suggestion.mention.morechannels": "Andere Kanäle",
"suggestion.mention.nonmembers": "Nicht im Kanal",
"suggestion.mention.special": "Spezielle Erwähnungen",
"suggestion.mention.you": "(Sie)",
"suggestion.mention.you": " (Sie)",
"suggestion.search.direct": "Direktnachricht",
"suggestion.search.private": "Private Kanäle",
"suggestion.search.public": "Öffentliche Kanäle",
@@ -649,6 +688,7 @@
"user.settings.general.lastName": "Nachname",
"user.settings.general.nickname": "Spitzname",
"user.settings.general.position": "Position",
"user.settings.general.status": "Status",
"user.settings.general.username": "Benutzername",
"user.settings.modal.display": "Anzeige",
"user.settings.modal.notifications": "Benachrichtigungen",

View File

@@ -664,7 +664,7 @@
"suggestion.mention.morechannels": "Other Channels",
"suggestion.mention.nonmembers": "Not in Channel",
"suggestion.mention.special": "Special Mentions",
"suggestion.mention.you": "(you)",
"suggestion.mention.you": " (you)",
"suggestion.search.direct": "Direct Messages",
"suggestion.search.private": "Private Channels",
"suggestion.search.public": "Public Channels",

View File

@@ -664,7 +664,7 @@
"suggestion.mention.morechannels": "Otros Canales",
"suggestion.mention.nonmembers": "No en el Canal",
"suggestion.mention.special": "Menciones especiales",
"suggestion.mention.you": "(tú)",
"suggestion.mention.you": " (tu)",
"suggestion.search.direct": "Mensajes Directos",
"suggestion.search.private": "Canales Privados",
"suggestion.search.public": "Canales Públicos",

View File

@@ -1,26 +1,26 @@
{
"about.date": "Kiadás dátuma:",
"about.date": "Build dátuma:",
"about.enterpriseEditionLearn": "Tudjon meg többet az Enterprise kiadásról: ",
"about.enterpriseEditionSt": "Modern kommunikáció a tűzfalad mögött.",
"about.enterpriseEditionSt": "Modern kommunikáció a tűzfalad mögül.",
"about.enterpriseEditione1": "Enterprise Kiadás",
"about.hash": "Build ujjlenyomata:",
"about.hashee": "EE build ujjlenyomata:",
"about.teamEditionLearn": "Csatlakozz a Mattermost közösséghez a ",
"about.teamEditionLearn": "Csatlakozz a Mattermost közösséghez itt: ",
"about.teamEditionSt": "Az összes csapaton belüli kommunikáció egy helyen, azonnal kereshetően és bárhonnan hozzáférhetően.",
"about.teamEditiont0": "Team kiadás",
"about.teamEditiont1": "Enterprise kiadás",
"about.title": "Névjegy {appTitle}",
"announcment_banner.dont_show_again": "Ne mutassa még egyszer",
"about.title": "{appTitle} névjegye",
"announcment_banner.dont_show_again": "Ne mutassa újra",
"api.channel.add_member.added": "{addedUsername} hozzá lett adva a csatornához {username} által.",
"apps.error": "Hiba: {error}",
"apps.error.command.field_missing": "Hiányzó kötelező mezők: `{fieldName}`.",
"apps.error.command.unknown_channel": "Ismeretlen csatorna a `{fieldName}` mezőhöz: `{option}`.",
"apps.error.command.unknown_option": "Ismeretlen opció a `{fieldName}`mezőhöz: `{option}`.",
"apps.error.command.unknown_user": "Ismeretlen felhasználó a `{fieldName}`mezőhöz: `{option}`.",
"apps.error.form.no_call": "`call` nincs meghatározva.",
"apps.error.form.no_form": "`form` nincs meghatározva.",
"apps.error.form.no_lookup_call": "performLookupCall props.call nincs meghatározva",
"apps.error.form.refresh": "Hiba történt a kiválasztás mezők letöltésekor. Lépjen kapcsolatba az alkalmazás fejlesztővel. Részletek: {details}",
"apps.error.form.no_call": "`call` nincs megadva.",
"apps.error.form.no_form": "`form` nincs megadva.",
"apps.error.form.no_lookup_call": "performLookupCall props.call nincs megadva",
"apps.error.form.refresh": "Hiba történt a kiválasztás mezők letöltésekor. Lépjen kapcsolatba az alkalmazás fejlesztővel. Részletek: {details}",
"apps.error.form.refresh_no_refresh": "Frissítést hívott egy frissítés nélküli mezőn.",
"apps.error.form.submit.pretext": "Hiba történt a modál elküldése során. Lépjen kapcsolatba az alkalmazás fejlesztőjével. Részletek: {details}",
"apps.error.lookup.error_preparing_request": "Hiba a keresési kérelem előkészítése során: {errorMessage}",
@@ -331,7 +331,7 @@
"mobile.gallery.title": "{index}/{total}",
"mobile.general.error.title": "Hiba",
"mobile.help.title": "Súgó",
"mobile.intro_messages.DM": "Ezzel megkezdődik a (z) {teammate} közvetlen üzeneteinek előzménye. Az itt megosztott közvetlen üzenetek és fájlok nem jelennek meg az ezen a területen kívüli személyek.",
"mobile.intro_messages.DM": "Ezzel megkezdődik a(z) {teammate} közvetlen üzeneteinek előzménye. Az itt megosztott közvetlen üzenetek és fájlok nem jelennek meg az ezen a területen kívüli személyek számára.",
"mobile.intro_messages.default_message": "Ez az első csatorna, amelyet a csapattársak látnak, amikor feliratkoznak - használja úgy, mint egy információs csatornát.",
"mobile.intro_messages.default_welcome": "Üdvözöllek a {name}!",
"mobile.ios.photos_permission_denied_description": "Töltsön fel fényképeket és videókat a Mattermost szerverére, vagy mentse azokat a készülékére. Nyissa meg a Beállításokat az olvasási és írási engedély biztosításához a fotó- és videotárhoz.",
@@ -357,7 +357,7 @@
"mobile.markdown.code.copy_code": "Kód másolása",
"mobile.markdown.code.plusMoreLines": "+{count, number} több {count, plural, one {line} other {lines}}",
"mobile.markdown.image.too_large": "A kép meghaladja a maximális méretét (maximum szélesség: {maxWidth}, maximum magasság {maxHeight}):",
"mobile.markdown.link.copy_url": "Link másolása",
"mobile.markdown.link.copy_url": "URL másolása",
"mobile.mention.copy_mention": "Említés másolása",
"mobile.message_length.message": "A jelenlegi üzenete túl hosszú. Jelenlegi karakterek száma: {count}/{max}",
"mobile.message_length.message_split_left": "Az üzenet meghaladja a karakterkorlátot",
@@ -664,7 +664,7 @@
"suggestion.mention.morechannels": "Egyéb csatornák",
"suggestion.mention.nonmembers": "Nincs a csatornában",
"suggestion.mention.special": "Külön említések",
"suggestion.mention.you": "(Ön)",
"suggestion.mention.you": " (te)",
"suggestion.search.direct": "Közvetlen üzenetek",
"suggestion.search.private": "Privát csatornák",
"suggestion.search.public": "Nyilvános csatornák",

View File

@@ -664,7 +664,7 @@
"suggestion.mention.morechannels": "他のチャンネル",
"suggestion.mention.nonmembers": "チャンネルにいません",
"suggestion.mention.special": "特殊な誰かについての投稿",
"suggestion.mention.you": "(あなた)",
"suggestion.mention.you": " (あなた)",
"suggestion.search.direct": "ダイレクトメッセージ",
"suggestion.search.private": "非公開チャンネル",
"suggestion.search.public": "公開チャンネル",

View File

@@ -664,7 +664,7 @@
"suggestion.mention.morechannels": "Andere Kanalen",
"suggestion.mention.nonmembers": "Niet in kanaal",
"suggestion.mention.special": "Speciale Vermeldingen",
"suggestion.mention.you": "(jij)",
"suggestion.mention.you": " (jij)",
"suggestion.search.direct": "Privé bericht",
"suggestion.search.private": "Privé-kanalen",
"suggestion.search.public": "Publieke kanalen",

View File

@@ -664,7 +664,7 @@
"suggestion.mention.morechannels": "Diğer kanallar",
"suggestion.mention.nonmembers": "Kanalda değil",
"suggestion.mention.special": "Özel anmalar",
"suggestion.mention.you": "(siz)",
"suggestion.mention.you": " (siz)",
"suggestion.search.direct": "Doğrudan iletiler",
"suggestion.search.private": "Özel kanallar",
"suggestion.search.public": "Herkese Açık Kanallar",

View File

@@ -664,7 +664,7 @@
"suggestion.mention.morechannels": "其他频道",
"suggestion.mention.nonmembers": "不在频道中",
"suggestion.mention.special": "特别提及",
"suggestion.mention.you": "(您)",
"suggestion.mention.you": " (您)",
"suggestion.search.direct": "私信",
"suggestion.search.private": "私有频道",
"suggestion.search.public": "公共频道",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -2,12 +2,12 @@ GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.3)
addressable (2.7.0)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.1.1)
aws-partitions (1.471.0)
aws-partitions (1.474.0)
aws-sdk-core (3.115.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
@@ -35,13 +35,15 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.2)
excon (0.82.0)
faraday (1.4.3)
excon (0.84.0)
faraday (1.5.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0.1)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
faraday-patron (~> 1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
@@ -50,12 +52,14 @@ GEM
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.1.0)
faraday-patron (1.0.0)
faraday_middleware (1.0.0)
faraday (~> 1.0)
fastimage (2.2.4)
fastlane (2.186.0)
fastlane (2.187.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
artifactory (~> 3.0)
@@ -99,31 +103,30 @@ GEM
fastlane-plugin-find_replace_string (0.1.0)
fastlane-plugin-versioning_android (0.1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.6.0)
google-apis-core (~> 0.1)
google-apis-core (0.3.0)
google-apis-androidpublisher_v3 (0.8.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.4.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.14)
httpclient (>= 2.8.1, < 3.0)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
retriable (>= 2.0, < 4.a)
rexml
signet (~> 0.14)
webrick
google-apis-iamcredentials_v1 (0.4.0)
google-apis-core (~> 0.1)
google-apis-playcustomapp_v1 (0.3.0)
google-apis-core (~> 0.1)
google-apis-storage_v1 (0.4.0)
google-apis-core (~> 0.1)
google-apis-iamcredentials_v1 (0.6.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-playcustomapp_v1 (0.5.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.6.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.1.0)
google-cloud-storage (1.32.0)
google-cloud-storage (1.34.0)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
@@ -160,7 +163,7 @@ GEM
plist (3.6.0)
public_suffix (4.0.6)
racc (1.5.2)
rake (13.0.3)
rake (13.0.4)
representable (3.1.1)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
@@ -169,7 +172,7 @@ GEM
rexml (3.2.5)
rouge (2.0.7)
ruby2_keywords (0.0.4)
rubyzip (2.3.0)
rubyzip (2.3.2)
security (0.1.3)
signet (0.15.0)
addressable (~> 2.3)
@@ -195,12 +198,13 @@ GEM
unicode-display_width (1.7.0)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.19.0)
xcodeproj (1.20.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)

View File

@@ -912,7 +912,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 360;
CURRENT_PROJECT_VERSION = 364;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -954,7 +954,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 360;
CURRENT_PROJECT_VERSION = 364;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;

View File

@@ -21,7 +21,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.44.1</string>
<string>1.45.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -37,7 +37,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>360</string>
<string>364</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.44.1</string>
<string>1.45.1</string>
<key>CFBundleVersion</key>
<string>360</string>
<string>364</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>

View File

@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.44.1</string>
<string>1.45.1</string>
<key>CFBundleVersion</key>
<string>360</string>
<string>364</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>

View File

@@ -693,7 +693,7 @@ SPEC CHECKSUMS:
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
FBReactNativeSpec: 74185edd6e8e5cb35512747bd840f8f9ad2381ff
FBReactNativeSpec: cef0cc6d50abc92e8cf52f140aa22b5371cfec0b
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
jail-monkey: 01cd0a75aa1034d08fd851869e6e6c3b063242d7
libwebp: e90b9c01d99205d03b6bb8f2c8c415e5a4ef66f0

2300
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "mattermost-mobile",
"version": "1.44.1",
"version": "1.45.1",
"description": "Mattermost Mobile with React Native",
"repository": "git@github.com:mattermost/mattermost-mobile.git",
"author": "Mattermost, Inc.",
@@ -19,8 +19,8 @@
"@rudderstack/rudder-sdk-react-native": "1.0.10",
"@sentry/react-native": "2.5.2",
"analytics-react-native": "1.2.0",
"commonmark": "github:mattermost/commonmark.js#d716e1c89e0a6721051df7bc74ad7683e1ae438f",
"commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#81af294317ebe19b5cc195d7fbc4f4a58177854c",
"commonmark": "0.30.0",
"commonmark-react-renderer": "4.3.5",
"deep-equal": "2.0.5",
"deepmerge": "4.2.2",
"emoji-regex": "9.2.2",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,351 @@
diff --git a/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js b/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js
index 91b0001..05b80fa 100644
--- a/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js
+++ b/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js
@@ -12,7 +12,12 @@ var typeAliases = {
htmlblock: 'html_block',
htmlinline: 'html_inline',
codeblock: 'code_block',
- hardbreak: 'linebreak'
+ hardbreak: 'linebreak',
+ atmention: 'at_mention',
+ channellink: 'channel_link',
+ editedindicator: 'edited_indicator',
+ tableRow: 'table_row',
+ tableCell: 'table_cell'
};
var defaultRenderers = {
@@ -24,6 +29,7 @@ var defaultRenderers = {
link: 'a',
paragraph: 'p',
strong: 'strong',
+ del: 'del',
thematic_break: 'hr', // eslint-disable-line camelcase
html_block: HtmlRenderer, // eslint-disable-line camelcase
@@ -52,7 +58,71 @@ var defaultRenderers = {
},
text: null,
- softbreak: null
+ softbreak: null,
+
+ at_mention: function AtMention(props) {
+ var newProps = getCoreProps(props);
+ if (props.username) {
+ props['data-mention-name'] = props.username;
+ }
+
+ return createElement('span', newProps, props.children);
+ },
+ channel_link: function ChannelLink(props) {
+ var newProps = getCoreProps(props);
+ if (props.channelName) {
+ props['data-channel-name'] = props.channelName;
+ }
+
+ return createElement('span', newProps, props.children);
+ },
+ emoji: function Emoji(props) {
+ var newProps = getCoreProps(props);
+ if (props.emojiName) {
+ props['data-emoji-name'] = props.emojiName;
+ }
+
+ return createElement('span', newProps, props.children);
+ },
+ edited_indicator: null,
+ hashtag: function Hashtag(props) {
+ var newProps = getCoreProps(props);
+ if (props.hashtag) {
+ props['data-hashtag'] = props.hashtag;
+ }
+
+ return createElement('span', newProps, props.children);
+ },
+ mention_highlight: function MentionHighlight(props) {
+ var newProps = getCoreProps(props);
+ newProps['data-mention-highlight'] = 'true';
+ return createElement('span', newProps, props.children);
+ },
+ search_highlight: function SearchHighlight(props) {
+ var newProps = getCoreProps(props);
+ newProps['data-search-highlight'] = 'true';
+ return createElement('span', newProps, props.children);
+ },
+
+ table: function Table(props) {
+ var childrenArray = React.Children.toArray(props.children);
+
+ var children = [createElement('thead', {'key': 'thead'}, childrenArray.slice(0, 1))];
+ if (childrenArray.length > 1) {
+ children.push(createElement('tbody', {'key': 'tbody'}, childrenArray.slice(1)));
+ }
+
+ return createElement('table', getCoreProps(props), children);
+ },
+ table_row: 'tr',
+ table_cell: function TableCell(props) {
+ var newProps = getCoreProps(props);
+ if (props.align) {
+ newProps.className = 'align-' + props.align;
+ }
+
+ return createElement('td', newProps, props.children);
+ }
};
var coreTypes = Object.keys(defaultRenderers);
@@ -147,7 +217,7 @@ function flattenPosition(pos) {
}
// For some nodes, we want to include more props than for others
-function getNodeProps(node, key, opts, renderer) {
+function getNodeProps(node, key, opts, renderer, context) {
var props = { key: key }, undef;
// `sourcePos` is true if the user wants source information (line/column info from markdown source)
@@ -194,16 +264,49 @@ function getNodeProps(node, key, opts, renderer) {
// Commonmark treats image description as children. We just want the text
props.alt = node.react.children.join('');
- node.react.children = undef;
break;
case 'list':
props.start = node.listStart;
props.type = node.listType;
props.tight = node.listTight;
break;
+ case 'at_mention':
+ props.mentionName = node.mentionName;
+ break;
+ case 'channel_link':
+ props.channelName = node.channelName;
+ break;
+ case 'emoji':
+ props.emojiName = node.emojiName;
+ props.literal = node.literal;
+ break;
+ case 'hashtag':
+ props.hashtag = node.hashtag;
+ break;
+ case 'paragraph':
+ props.first = !(node._prev && node._prev.type === 'paragraph');
+ props.last = !(node._next && node._next.type === 'paragraph');
+ break;
+ case 'edited_indicator':
+ break;
+ case 'table':
+ props.numRows = countRows(node);
+ props.numColumns = countColumns(node);
+ break;
+ case 'table_row':
+ props.isHeading = node.isHeading;
+ break;
+ case 'table_cell':
+ props.isHeading = node.isHeading;
+ props.align = node.align;
+ break;
default:
}
+ if (opts.getExtraPropsForNode) {
+ props = Object.assign(props, opts.getExtraPropsForNode(node));
+ }
+
if (typeof renderer !== 'string') {
props.literal = node.literal;
}
@@ -213,9 +316,29 @@ function getNodeProps(node, key, opts, renderer) {
props.children = children.reduce(reduceChildren, []) || null;
}
+ props.context = context.slice();
+
return props;
}
+function countChildren(node) {
+ var count = 0;
+
+ for (var child = node.firstChild; child; child = child.next) {
+ count += 1;
+ }
+
+ return count;
+}
+
+function countRows(table) {
+ return countChildren(table);
+}
+
+function countColumns(table) {
+ return countChildren(table.firstChild);
+}
+
function getPosition(node) {
if (!node) {
return null;
@@ -238,26 +361,23 @@ function renderNodes(block) {
transformLinkUri: this.transformLinkUri,
transformImageUri: this.transformImageUri,
softBreak: this.softBreak,
- linkTarget: this.linkTarget
+ linkTarget: this.linkTarget,
+ getExtraPropsForNode: this.getExtraPropsForNode
};
- var e, node, entering, leaving, type, doc, key, nodeProps, prevPos, prevIndex = 0;
+ var e;
+ var doc;
+ var context = [];
+ var index = 0;
while ((e = walker.next())) {
- var pos = getPosition(e.node.sourcepos ? e.node : e.node.parent);
- if (prevPos === pos) {
- key = pos + prevIndex;
- prevIndex++;
- } else {
- key = pos;
- prevIndex = 0;
- }
+ var key = String(index);
+ index += 1;
- prevPos = pos;
- entering = e.entering;
- leaving = !entering;
- node = e.node;
- type = normalizeTypeName(node.type);
- nodeProps = null;
+ var entering = e.entering;
+ var leaving = !entering;
+ var node = e.node;
+ var type = normalizeTypeName(node.type);
+ var nodeProps = null;
// If we have not assigned a document yet, assume the current node is just that
if (!doc) {
@@ -270,7 +390,7 @@ function renderNodes(block) {
}
// In HTML, we don't want paragraphs inside of list items
- if (type === 'paragraph' && isGrandChildOfList(node)) {
+ if (!this.renderParagraphsInLists && type === 'paragraph' && isGrandChildOfList(node)) {
continue;
}
@@ -289,7 +409,7 @@ function renderNodes(block) {
if (this.allowNode && (isCompleteParent || !node.isContainer)) {
var nodeChildren = isCompleteParent ? node.react.children : [];
- nodeProps = getNodeProps(node, key, propOptions, renderer);
+ nodeProps = getNodeProps(node, key, propOptions, renderer, context);
disallowedByUser = !this.allowNode({
type: pascalCase(type),
renderer: this.renderers[type],
@@ -298,6 +418,30 @@ function renderNodes(block) {
});
}
+ if (node.isContainer) {
+ var contextType = node.type;
+ if (node.level) {
+ contextType = node.type + node.level;
+ } else if (node.type === 'table_row' && node.parent.firstChild === node) {
+ contextType = 'table_header_row';
+ } else {
+ contextType = node.type;
+ }
+
+ if (entering) {
+ context.push(contextType);
+ } else {
+ var popped = context.pop();
+
+ if (!popped) {
+ throw new Error('Attempted to pop empty stack');
+ } else if (!popped === contextType) {
+ throw new Error('Popped context of type `' + pascalCase(popped) +
+ '` when expecting context of type `' + pascalCase(contextType) + '`');
+ }
+ }
+ }
+
if (!isDocument && (disallowedByUser || disallowedByConfig)) {
if (!this.unwrapDisallowed && entering && node.isContainer) {
walker.resumeAt(node, false);
@@ -313,15 +457,25 @@ function renderNodes(block) {
);
}
- if (node.isContainer && entering) {
+ if (context.length > this.maxDepth) {
+ // Do nothing, we should not regularly be nested this deeply and we don't want to cause React to
+ // overflow the stack
+ } else if (node.isContainer && entering) {
node.react = {
component: renderer,
props: {},
children: []
};
} else {
- var childProps = nodeProps || getNodeProps(node, key, propOptions, renderer);
- if (renderer) {
+ var childProps = nodeProps || getNodeProps(node, key, propOptions, renderer, context);
+ if (renderer === ReactRenderer.forwardChildren) {
+ if (childProps.children) {
+ for (var i = 0; i < childProps.children.length; i++) {
+ var child = childProps.children[i];
+ addChild(node, child);
+ }
+ }
+ } else if (renderer) {
childProps = typeof renderer === 'string'
? childProps
: assign(childProps, {nodeKey: childProps.key});
@@ -341,6 +495,10 @@ function renderNodes(block) {
}
}
+ if (context.length !== 0) {
+ throw new Error('Expected context to be empty after rendering, but has `' + context.join(', ') + '`');
+ }
+
return doc.react.children;
}
@@ -401,21 +559,31 @@ function ReactRenderer(options) {
renderers: assign({}, defaultRenderers, normalizeRenderers(opts.renderers)),
escapeHtml: Boolean(opts.escapeHtml),
skipHtml: Boolean(opts.skipHtml),
+ renderParagraphsInLists: Boolean(opts.renderParagraphsInLists),
transformLinkUri: linkFilter,
transformImageUri: imageFilter,
allowNode: opts.allowNode,
allowedTypes: allowedTypes,
unwrapDisallowed: Boolean(opts.unwrapDisallowed),
render: renderNodes,
- linkTarget: opts.linkTarget || false
+ linkTarget: opts.linkTarget || false,
+ maxDepth: opts.maxDepth || 30,
+ getExtraPropsForNode: opts.getExtraPropsForNode
};
}
+function forwardChildren(props) {
+ return props.children;
+}
+
ReactRenderer.uriTransformer = defaultLinkUriFilter;
ReactRenderer.types = coreTypes.map(pascalCase);
ReactRenderer.renderers = coreTypes.reduce(function(renderers, type) {
renderers[pascalCase(type)] = defaultRenderers[type];
return renderers;
}, {});
+ReactRenderer.countRows = countRows;
+ReactRenderer.countColumns = countColumns;
+ReactRenderer.forwardChildren = forwardChildren;
module.exports = ReactRenderer;

View File

@@ -1,3 +1,24 @@
diff --git a/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js b/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js
index a069a24..a21fa38 100644
--- a/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js
+++ b/node_modules/react-native/Libraries/Components/ScrollView/ScrollView.js
@@ -1222,9 +1222,15 @@ class ScrollView extends React.Component<Props, State> {
// Note: we should split props.style on the inner and outer props
// however, the ScrollView still needs the baseStyle to be scrollable
const {outer, inner} = splitLayoutProps(flattenStyle(props.style));
+ let inverted;
+ if (inner.scaleY) {
+ inverted = {scaleY: -1};
+ delete inner['scaleY']
+ }
+
return React.cloneElement(
refreshControl,
- {style: [baseStyle, outer]},
+ {style: [baseStyle, outer, inverted]},
<ScrollViewClass
{...props}
style={[baseStyle, inner]}
diff --git a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
index dffccc4..dc426c2 100644
--- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
@@ -40,10 +61,10 @@ index 2249d54..cc303b3 100644
horizontallyInverted: {
transform: [{scaleX: -1}],
diff --git a/node_modules/react-native/react.gradle b/node_modules/react-native/react.gradle
index 5995ad5..dbae7e3 100644
index dd34c98..4d74938 100644
--- a/node_modules/react-native/react.gradle
+++ b/node_modules/react-native/react.gradle
@@ -157,7 +157,7 @@ afterEvaluate {
@@ -151,7 +151,7 @@ afterEvaluate {
// Set up dev mode
def devEnabled = !(config."devDisabledIn${targetName}"
@@ -52,7 +73,7 @@ index 5995ad5..dbae7e3 100644
def extraArgs = config.extraPackagerArgs ?: [];
@@ -177,7 +177,7 @@ afterEvaluate {
@@ -171,7 +171,7 @@ afterEvaluate {
def hermesFlags;
def hbcTempFile = file("${jsBundleFile}.hbc")
exec {
@@ -61,7 +82,7 @@ index 5995ad5..dbae7e3 100644
// Can't use ?: since that will also substitute valid empty lists
hermesFlags = config.hermesFlagsRelease
if (hermesFlags == null) hermesFlags = ["-O", "-output-source-map"]
@@ -221,7 +221,7 @@ afterEvaluate {
@@ -215,7 +215,7 @@ afterEvaluate {
? config."bundleIn${targetName}"
: config."bundleIn${variant.buildType.name.capitalize()}" != null
? config."bundleIn${variant.buildType.name.capitalize()}"
@@ -70,7 +91,7 @@ index 5995ad5..dbae7e3 100644
}
// Expose a minimal interface on the application variant and the task itself:
@@ -318,7 +318,7 @@ afterEvaluate {
@@ -312,7 +312,7 @@ afterEvaluate {
// This should really be done by packaging all Hermes releated libs into
// two separate HermesDebug and HermesRelease AARs, but until then we'll
// kludge it by deleting the .so files out of the /transforms/ directory.