Merge beta into master (#1637)
|
@ -197,7 +197,8 @@ jobs:
|
||||||
npx jetify
|
npx jetify
|
||||||
cd android
|
cd android
|
||||||
if [[ $KEYSTORE ]]; then
|
if [[ $KEYSTORE ]]; then
|
||||||
./gradlew bundleRelease
|
# TODO: enable app bundle again
|
||||||
|
./gradlew assembleRelease
|
||||||
else
|
else
|
||||||
./gradlew assembleDebug
|
./gradlew assembleDebug
|
||||||
fi
|
fi
|
||||||
|
|
40
README.md
|
@ -1,4 +1,4 @@
|
||||||
# Rocket.Chat React Native Mobile
|
# Rocket.Chat Mobile
|
||||||
|
|
||||||
[](https://david-dm.org/RocketChat/Rocket.Chat.ReactNative)
|
[](https://david-dm.org/RocketChat/Rocket.Chat.ReactNative)
|
||||||
[](https://www.codacy.com/app/RocketChat/Rocket.Chat.ReactNative?utm_source=github.com&utm_medium=referral&utm_content=RocketChat/Rocket.Chat.ReactNative&utm_campaign=badger)
|
[](https://www.codacy.com/app/RocketChat/Rocket.Chat.ReactNative?utm_source=github.com&utm_medium=referral&utm_content=RocketChat/Rocket.Chat.ReactNative&utm_campaign=badger)
|
||||||
|
@ -8,6 +8,16 @@
|
||||||
**Supported Server Versions:** 0.70.0+
|
**Supported Server Versions:** 0.70.0+
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
|
### Official apps
|
||||||
|
<a href="https://play.google.com/store/apps/details?id=chat.rocket.android">
|
||||||
|
<img alt="Download on Google Play" src="https://play.google.com/intl/en_us/badges/images/badge_new.png" height=43>
|
||||||
|
</a>
|
||||||
|
<a href="https://apps.apple.com/us/app/rocket-chat/id1148741252">
|
||||||
|
<img alt="Download on App Store" src="https://user-images.githubusercontent.com/7317008/43209852-4ca39622-904b-11e8-8ce1-cdc3aee76ae9.png" height=43>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### Experimental apps
|
||||||
<a href="https://play.google.com/store/apps/details?id=chat.rocket.reactnative">
|
<a href="https://play.google.com/store/apps/details?id=chat.rocket.reactnative">
|
||||||
<img alt="Download on Google Play" src="https://play.google.com/intl/en_us/badges/images/badge_new.png" height=43>
|
<img alt="Download on Google Play" src="https://play.google.com/intl/en_us/badges/images/badge_new.png" height=43>
|
||||||
</a>
|
</a>
|
||||||
|
@ -19,11 +29,17 @@
|
||||||
|
|
||||||
### TestFlight
|
### TestFlight
|
||||||
|
|
||||||
You can signup to our TestFlight builds by acessing this link: https://testflight.apple.com/join/7I3dLCNT.
|
You can signup to our TestFlight builds by accessing these links:
|
||||||
|
|
||||||
|
- Official: https://testflight.apple.com/join/3gcYeoMr
|
||||||
|
- Experimental: https://testflight.apple.com/join/7I3dLCNT.
|
||||||
|
|
||||||
### Google Play beta
|
### Google Play beta
|
||||||
|
|
||||||
You can subscribe to Google Play Beta program and download latest versions: https://play.google.com/store/apps/details?id=chat.rocket.reactnative
|
You can subscribe to Google Play Beta program and download latest versions:
|
||||||
|
|
||||||
|
- Official: https://play.google.com/store/apps/details?id=chat.rocket.android
|
||||||
|
- Experimental: https://play.google.com/store/apps/details?id=chat.rocket.reactnative
|
||||||
|
|
||||||
## Reporting an Issue
|
## Reporting an Issue
|
||||||
|
|
||||||
|
@ -57,13 +73,8 @@ If you don't need multiple servers, there is a branch `single-server` just for t
|
||||||
Readme will guide you on how to config.
|
Readme will guide you on how to config.
|
||||||
|
|
||||||
## Current priorities
|
## Current priorities
|
||||||
1) Jitsi integration
|
1) Omnichannel support
|
||||||
2) Notification Preferences
|
2) E2E encryption
|
||||||
3) Two-way authentication
|
|
||||||
4) Bugsnag
|
|
||||||
5) Optional Analytics
|
|
||||||
6) Typescript
|
|
||||||
7) Prettier
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
| Feature | Status |
|
| Feature | Status |
|
||||||
|
@ -71,6 +82,7 @@ Readme will guide you on how to config.
|
||||||
| Jitsi Integration | ✅ |
|
| Jitsi Integration | ✅ |
|
||||||
| Federation (Directory) | ✅ |
|
| Federation (Directory) | ✅ |
|
||||||
| Discussions | ❌ |
|
| Discussions | ❌ |
|
||||||
|
| Omnichannel | ❌ |
|
||||||
| Threads | ✅ |
|
| Threads | ✅ |
|
||||||
| Record Audio | ✅ |
|
| Record Audio | ✅ |
|
||||||
| Record Video | ✅ |
|
| Record Video | ✅ |
|
||||||
|
@ -83,18 +95,18 @@ Readme will guide you on how to config.
|
||||||
| Grouped messages | ✅ |
|
| Grouped messages | ✅ |
|
||||||
| Mark room as read | ✅ |
|
| Mark room as read | ✅ |
|
||||||
| Mark room as unread | ✅ |
|
| Mark room as unread | ✅ |
|
||||||
| Tablet Support | ❌ |
|
| Tablet Support | ✅ |
|
||||||
| Read receipt | ✅ |
|
| Read receipt | ✅ |
|
||||||
| Broadbast Channel | ✅ |
|
| Broadbast Channel | ✅ |
|
||||||
| Authentication via SAML | ✅ |
|
| Authentication via SAML | ✅ |
|
||||||
| Authentication via CAS | ✅ |
|
| Authentication via CAS | ✅ |
|
||||||
| Custom Fields on Signup | ✅ |
|
| Custom Fields on Signup | ✅ |
|
||||||
| Report message | ✅ |
|
| Report message | ✅ |
|
||||||
| Theming | ❌ |
|
| Theming | ✅ |
|
||||||
| Settings -> Review the App | ❌ |
|
| Settings -> Review the App | ❌ |
|
||||||
| Settings -> Default Browser | ❌ |
|
| Settings -> Default Browser | ❌ |
|
||||||
| Admin panel | ✅ |
|
| Admin panel | ✅ |
|
||||||
| Reply message from notification | ❌ |
|
| Reply message from notification | ✅ |
|
||||||
| Unread counter banner on message list | ✅ |
|
| Unread counter banner on message list | ✅ |
|
||||||
| E2E Encryption | ❌ |
|
| E2E Encryption | ❌ |
|
||||||
| Join a Protected Room | ❌ |
|
| Join a Protected Room | ❌ |
|
||||||
|
@ -106,7 +118,7 @@ Readme will guide you on how to config.
|
||||||
| Accessibility (Medium) | ❌ |
|
| Accessibility (Medium) | ❌ |
|
||||||
| Accessibility (Advanced) | ❌ |
|
| Accessibility (Advanced) | ❌ |
|
||||||
| Authentication via Meteor | ❌ |
|
| Authentication via Meteor | ❌ |
|
||||||
| Authentication via Wordpress | ❌ |
|
| Authentication via Wordpress | ✅ |
|
||||||
| Authentication via Custom OAuth | ✅ |
|
| Authentication via Custom OAuth | ✅ |
|
||||||
| Add user to the room | ✅ |
|
| Add user to the room | ✅ |
|
||||||
| Send message | ✅ |
|
| Send message | ✅ |
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
# To learn about Buck see [Docs](https://buckbuild.com/).
|
|
||||||
# To run your application with Buck:
|
|
||||||
# - install Buck
|
|
||||||
# - `npm start` - to start the packager
|
|
||||||
# - `cd android`
|
|
||||||
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
|
|
||||||
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
|
|
||||||
# - `buck install -r android/app` - compile, install and run application
|
|
||||||
#
|
|
||||||
|
|
||||||
load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
|
|
||||||
|
|
||||||
lib_deps = []
|
|
||||||
|
|
||||||
create_aar_targets(glob(["libs/*.aar"]))
|
|
||||||
|
|
||||||
create_jar_targets(glob(["libs/*.jar"]))
|
|
||||||
|
|
||||||
android_library(
|
|
||||||
name = "all-libs",
|
|
||||||
exported_deps = lib_deps,
|
|
||||||
)
|
|
||||||
|
|
||||||
android_library(
|
|
||||||
name = "app-code",
|
|
||||||
srcs = glob([
|
|
||||||
"src/main/java/**/*.java",
|
|
||||||
]),
|
|
||||||
deps = [
|
|
||||||
":all-libs",
|
|
||||||
":build_config",
|
|
||||||
":res",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
android_build_config(
|
|
||||||
name = "build_config",
|
|
||||||
package = "chat.rocket.reactnative",
|
|
||||||
)
|
|
||||||
|
|
||||||
android_resource(
|
|
||||||
name = "res",
|
|
||||||
package = "chat.rocket.reactnative",
|
|
||||||
res = "src/main/res",
|
|
||||||
)
|
|
||||||
|
|
||||||
android_binary(
|
|
||||||
name = "app",
|
|
||||||
keystore = "//android/keystores:debug",
|
|
||||||
manifest = "src/main/AndroidManifest.xml",
|
|
||||||
package_type = "debug",
|
|
||||||
deps = [
|
|
||||||
":app-code",
|
|
||||||
],
|
|
||||||
)
|
|
|
@ -83,7 +83,7 @@ project.ext.react = [
|
||||||
entryFile: "index.js",
|
entryFile: "index.js",
|
||||||
bundleAssetName: "app.bundle",
|
bundleAssetName: "app.bundle",
|
||||||
iconFontNames: [ 'custom.ttf' ],
|
iconFontNames: [ 'custom.ttf' ],
|
||||||
enableHermes: false, // clean and rebuild if changing
|
enableHermes: true, // clean and rebuild if changing
|
||||||
]
|
]
|
||||||
|
|
||||||
apply from: "../../node_modules/react-native/react.gradle"
|
apply from: "../../node_modules/react-native/react.gradle"
|
||||||
|
@ -138,7 +138,7 @@ android {
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode VERSIONCODE as Integer
|
versionCode VERSIONCODE as Integer
|
||||||
versionName "1.25.0"
|
versionName "4.3.0"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||||
}
|
}
|
||||||
|
@ -217,6 +217,10 @@ dependencies {
|
||||||
} else {
|
} else {
|
||||||
implementation jscFlavor
|
implementation jscFlavor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implementation "com.google.code.gson:gson:2.8.5"
|
||||||
|
implementation "com.github.bumptech.glide:glide:4.9.0"
|
||||||
|
annotationProcessor "com.github.bumptech.glide:compiler:4.9.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run this once to be able to run the application with BUCK
|
// Run this once to be able to run the application with BUCK
|
||||||
|
|
|
@ -20,14 +20,21 @@
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
>
|
>
|
||||||
<activity
|
<activity
|
||||||
|
android:name="com.zoontek.rnbootsplash.RNBootSplashActivity"
|
||||||
|
android:theme="@style/BootTheme">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
<action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
|
<action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter android:label="@string/app_name">
|
<intent-filter android:label="@string/app_name">
|
||||||
|
@ -40,6 +47,15 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||||
|
<receiver
|
||||||
|
android:name=".ReplyBroadcast"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
|
<receiver
|
||||||
|
android:name=".DismissNotification"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" >
|
||||||
|
</receiver>
|
||||||
<activity
|
<activity
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:name=".share.ShareActivity"
|
android:name=".share.ShareActivity"
|
||||||
|
|
|
@ -4,56 +4,139 @@ import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.app.RemoteInput;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings.System;
|
|
||||||
import android.media.RingtoneManager;
|
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||||
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.lang.InterruptedException;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.wix.reactnativenotifications.core.AppLaunchHelper;
|
import com.wix.reactnativenotifications.core.AppLaunchHelper;
|
||||||
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
|
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
|
||||||
import com.wix.reactnativenotifications.core.JsIOHelper;
|
import com.wix.reactnativenotifications.core.JsIOHelper;
|
||||||
import com.wix.reactnativenotifications.core.notification.PushNotification;
|
import com.wix.reactnativenotifications.core.notification.PushNotification;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
|
||||||
|
|
||||||
public class CustomPushNotification extends PushNotification {
|
public class CustomPushNotification extends PushNotification {
|
||||||
|
public static ReactApplicationContext reactApplicationContext;
|
||||||
|
|
||||||
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
|
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
|
||||||
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
|
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
|
||||||
|
reactApplicationContext = new ReactApplicationContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, List<String>> notificationMessages = new HashMap<String, List<String>>();
|
||||||
|
public static String KEY_REPLY = "KEY_REPLY";
|
||||||
|
public static String NOTIFICATION_ID = "NOTIFICATION_ID";
|
||||||
|
|
||||||
|
public static void clearMessages(int notId) {
|
||||||
|
notificationMessages.remove(Integer.toString(notId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceived() throws InvalidNotificationException {
|
||||||
|
final Bundle bundle = mNotificationProps.asBundle();
|
||||||
|
|
||||||
|
String notId = bundle.getString("notId");
|
||||||
|
String message = bundle.getString("message");
|
||||||
|
|
||||||
|
if (notificationMessages.get(notId) == null) {
|
||||||
|
notificationMessages.put(notId, new ArrayList<String>());
|
||||||
|
}
|
||||||
|
notificationMessages.get(notId).add(message);
|
||||||
|
|
||||||
|
super.postNotification(notId != null ? Integer.parseInt(notId) : 1);
|
||||||
|
|
||||||
|
notifyReceivedToJS();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpened() {
|
||||||
|
Bundle bundle = mNotificationProps.asBundle();
|
||||||
|
final String notId = bundle.getString("notId");
|
||||||
|
notificationMessages.remove(notId);
|
||||||
|
digestNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
|
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
|
||||||
final Resources res = mContext.getResources();
|
final Notification.Builder notification = new Notification.Builder(mContext);
|
||||||
String packageName = mContext.getPackageName();
|
|
||||||
|
|
||||||
Bundle bundle = mNotificationProps.asBundle();
|
Bundle bundle = mNotificationProps.asBundle();
|
||||||
int smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
|
|
||||||
int largeIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
|
|
||||||
String title = bundle.getString("title");
|
String title = bundle.getString("title");
|
||||||
String message = bundle.getString("message");
|
String message = bundle.getString("message");
|
||||||
|
String notId = bundle.getString("notId");
|
||||||
|
|
||||||
String CHANNEL_ID = "rocketchatrn_channel_01";
|
notification
|
||||||
String CHANNEL_NAME = "All";
|
|
||||||
|
|
||||||
final Notification.Builder notification = new Notification.Builder(mContext)
|
|
||||||
.setSmallIcon(smallIconResId)
|
|
||||||
.setContentIntent(intent)
|
.setContentIntent(intent)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(message)
|
.setContentText(message)
|
||||||
.setStyle(new Notification.BigTextStyle().bigText(message))
|
|
||||||
.setPriority(Notification.PRIORITY_HIGH)
|
.setPriority(Notification.PRIORITY_HIGH)
|
||||||
.setDefaults(Notification.DEFAULT_ALL)
|
.setDefaults(Notification.DEFAULT_ALL)
|
||||||
.setAutoCancel(true);
|
.setAutoCancel(true);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
Integer notificationId = notId != null ? Integer.parseInt(notId) : 1;
|
||||||
notification.setColor(mContext.getColor(R.color.notification_text));
|
notificationChannel(notification);
|
||||||
|
notificationIcons(notification, bundle);
|
||||||
|
notificationStyle(notification, notificationId, bundle);
|
||||||
|
notificationReply(notification, notificationId, bundle);
|
||||||
|
notificationDismiss(notification, notificationId);
|
||||||
|
|
||||||
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void notifyReceivedToJS() {
|
||||||
|
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap getAvatar(String uri) {
|
||||||
|
try {
|
||||||
|
return Glide.with(mContext)
|
||||||
|
.asBitmap()
|
||||||
|
.apply(RequestOptions.bitmapTransform(new RoundedCorners(10)))
|
||||||
|
.load(uri)
|
||||||
|
.submit(100, 100)
|
||||||
|
.get();
|
||||||
|
} catch (final ExecutionException | InterruptedException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notificationIcons(Notification.Builder notification, Bundle bundle) {
|
||||||
|
final Resources res = mContext.getResources();
|
||||||
|
String packageName = mContext.getPackageName();
|
||||||
|
|
||||||
|
int smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
|
||||||
|
|
||||||
|
Gson gson = new Gson();
|
||||||
|
Ejson ejson = gson.fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
||||||
|
|
||||||
|
notification
|
||||||
|
.setSmallIcon(smallIconResId)
|
||||||
|
.setLargeIcon(getAvatar(ejson.getAvatarUri()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notificationChannel(Notification.Builder notification) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
String CHANNEL_ID = "rocketchatrn_channel_01";
|
||||||
|
String CHANNEL_NAME = "All";
|
||||||
|
|
||||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
|
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
|
||||||
CHANNEL_NAME,
|
CHANNEL_NAME,
|
||||||
NotificationManager.IMPORTANCE_DEFAULT);
|
NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
@ -63,10 +146,65 @@ public class CustomPushNotification extends PushNotification {
|
||||||
|
|
||||||
notification.setChannelId(CHANNEL_ID);
|
notification.setChannelId(CHANNEL_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
|
|
||||||
notification.setLargeIcon(largeIconBitmap);
|
|
||||||
|
|
||||||
return notification;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void notificationStyle(Notification.Builder notification, int notId, Bundle bundle) {
|
||||||
|
Notification.InboxStyle messageStyle = new Notification.InboxStyle();
|
||||||
|
List<String> messages = notificationMessages.get(Integer.toString(notId));
|
||||||
|
if (messages != null) {
|
||||||
|
for (int i = 0; i < messages.size(); i++) {
|
||||||
|
messageStyle.addLine(messages.get(i));
|
||||||
|
}
|
||||||
|
String summary = bundle.getString("summaryText");
|
||||||
|
messageStyle.setSummaryText(summary.replace("%n%", Integer.toString(messages.size())));
|
||||||
|
notification.setNumber(messages.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
notification.setColor(mContext.getColor(R.color.notification_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.setStyle(messageStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notificationReply(Notification.Builder notification, int notificationId, Bundle bundle) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String label = "Reply";
|
||||||
|
|
||||||
|
final Resources res = mContext.getResources();
|
||||||
|
String packageName = mContext.getPackageName();
|
||||||
|
int smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
|
||||||
|
|
||||||
|
Intent replyIntent = new Intent(mContext, ReplyBroadcast.class);
|
||||||
|
replyIntent.setAction(KEY_REPLY);
|
||||||
|
replyIntent.putExtra("pushNotification", bundle);
|
||||||
|
|
||||||
|
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
|
||||||
|
.setLabel(label)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CharSequence title = label;
|
||||||
|
Notification.Action replyAction = new Notification.Action.Builder(smallIconResId, title, replyPendingIntent)
|
||||||
|
.addRemoteInput(remoteInput)
|
||||||
|
.setAllowGeneratedReplies(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
notification
|
||||||
|
.setShowWhen(true)
|
||||||
|
.addAction(replyAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notificationDismiss(Notification.Builder notification, int notificationId) {
|
||||||
|
Intent intent = new Intent(mContext, DismissNotification.class);
|
||||||
|
intent.putExtra(NOTIFICATION_ID, notificationId);
|
||||||
|
|
||||||
|
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, intent, 0);
|
||||||
|
|
||||||
|
notification.setDeleteIntent(dismissPendingIntent);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package chat.rocket.reactnative;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
public class DismissNotification extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
int notId = intent.getExtras().getInt(CustomPushNotification.NOTIFICATION_ID);
|
||||||
|
CustomPushNotification.clearMessages(notId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package chat.rocket.reactnative;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import chat.rocket.userdefaults.RNUserDefaultsModule;
|
||||||
|
|
||||||
|
public class Ejson {
|
||||||
|
String host;
|
||||||
|
String rid;
|
||||||
|
String type;
|
||||||
|
Sender sender;
|
||||||
|
|
||||||
|
private String TOKEN_KEY = "reactnativemeteor_usertoken-";
|
||||||
|
private SharedPreferences sharedPreferences = RNUserDefaultsModule.getPreferences(CustomPushNotification.reactApplicationContext);
|
||||||
|
|
||||||
|
public String getAvatarUri() {
|
||||||
|
if (type == null || !type.equals("d")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return serverURL() + "/avatar/" + this.sender.username + "?rc_token=" + token() + "&rc_uid=" + userId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String token() {
|
||||||
|
return sharedPreferences.getString(TOKEN_KEY.concat(userId()), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String userId() {
|
||||||
|
return sharedPreferences.getString(TOKEN_KEY.concat(serverURL()), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String serverURL() {
|
||||||
|
String url = this.host;
|
||||||
|
if (url.endsWith("/")) {
|
||||||
|
url = url.substring(0, url.length() - 1);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Sender {
|
||||||
|
String username;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,16 +5,16 @@ import com.facebook.react.ReactRootView;
|
||||||
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import com.facebook.react.ReactFragmentActivity;
|
import com.facebook.react.ReactFragmentActivity;
|
||||||
import org.devio.rn.splashscreen.SplashScreen;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
import com.zoontek.rnbootsplash.RNBootSplash;
|
||||||
|
|
||||||
public class MainActivity extends ReactFragmentActivity {
|
public class MainActivity extends ReactFragmentActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
SplashScreen.show(this);
|
super.onCreate(savedInstanceState);
|
||||||
super.onCreate(null);
|
RNBootSplash.show(R.drawable.launch_screen, MainActivity.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
package chat.rocket.reactnative;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.RemoteInput;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
import chat.rocket.userdefaults.RNUserDefaultsModule;
|
||||||
|
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
||||||
|
|
||||||
|
public class ReplyBroadcast extends BroadcastReceiver {
|
||||||
|
private Context mContext;
|
||||||
|
private Bundle bundle;
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||||
|
final CharSequence message = getReplyMessage(intent);
|
||||||
|
if (message == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mContext = context;
|
||||||
|
bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
|
||||||
|
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
String notId = bundle.getString("notId");
|
||||||
|
|
||||||
|
Gson gson = new Gson();
|
||||||
|
Ejson ejson = gson.fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
||||||
|
|
||||||
|
replyToMessage(ejson, Integer.parseInt(notId), message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void replyToMessage(final Ejson ejson, final int notId, final CharSequence message) {
|
||||||
|
String serverURL = ejson.serverURL();
|
||||||
|
String rid = ejson.rid;
|
||||||
|
|
||||||
|
if (serverURL == null || rid == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final OkHttpClient client = new OkHttpClient();
|
||||||
|
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||||
|
|
||||||
|
String json = buildMessage(rid, message.toString());
|
||||||
|
|
||||||
|
CustomPushNotification.clearMessages(notId);
|
||||||
|
|
||||||
|
RequestBody body = RequestBody.create(JSON, json);
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.header("x-auth-token", ejson.token())
|
||||||
|
.header("x-user-id", ejson.userId())
|
||||||
|
.url(String.format("%s/api/v1/chat.sendMessage", serverURL))
|
||||||
|
.post(body)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new okhttp3.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
Log.i("RCNotification", String.format("Reply FAILED exception %s", e.getMessage()));
|
||||||
|
onReplyFailed(notificationManager, notId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, final Response response) throws IOException {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
Log.d("RCNotification", "Reply SUCCESS");
|
||||||
|
onReplySuccess(notificationManager, notId);
|
||||||
|
} else {
|
||||||
|
Log.i("RCNotification", String.format("Reply FAILED status %s BODY %s", response.code(), response.body().string()));
|
||||||
|
onReplyFailed(notificationManager, notId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMessageId() {
|
||||||
|
final String ALPHA_NUMERIC_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
int count = 17;
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
while (count-- != 0) {
|
||||||
|
int character = (int)(Math.random()*ALPHA_NUMERIC_STRING.length());
|
||||||
|
builder.append(ALPHA_NUMERIC_STRING.charAt(character));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String buildMessage(String rid, String message) {
|
||||||
|
Gson gsonBuilder = new GsonBuilder().create();
|
||||||
|
|
||||||
|
Map msgMap = new HashMap();
|
||||||
|
msgMap.put("_id", getMessageId());
|
||||||
|
msgMap.put("rid", rid);
|
||||||
|
msgMap.put("msg", message);
|
||||||
|
msgMap.put("tmid", null);
|
||||||
|
|
||||||
|
Map msg = new HashMap();
|
||||||
|
msg.put("message", msgMap);
|
||||||
|
|
||||||
|
String json = gsonBuilder.toJson(msg);
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onReplyFailed(NotificationManager notificationManager, int notId) {
|
||||||
|
String CHANNEL_ID = "CHANNEL_ID_REPLY_FAILED";
|
||||||
|
|
||||||
|
final Resources res = mContext.getResources();
|
||||||
|
String packageName = mContext.getPackageName();
|
||||||
|
int smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
|
||||||
|
|
||||||
|
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
Notification notification =
|
||||||
|
new Notification.Builder(mContext, CHANNEL_ID)
|
||||||
|
.setContentTitle("Failed to reply message.")
|
||||||
|
.setSmallIcon(smallIconResId)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
notificationManager.notify(notId, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onReplySuccess(NotificationManager notificationManager, int notId) {
|
||||||
|
notificationManager.cancel(notId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence getReplyMessage(Intent intent) {
|
||||||
|
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||||
|
if (remoteInput != null) {
|
||||||
|
return remoteInput.getCharSequence(CustomPushNotification.KEY_REPLY);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 30 KiB |
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
||||||
|
<!-- the background color. it can be a system color or a custom one defined in colors.xml -->
|
||||||
|
<item android:drawable="@color/splashBackground" />
|
||||||
|
<item>
|
||||||
|
<!-- the app logo, centered horizontally and vertically -->
|
||||||
|
<bitmap
|
||||||
|
android:src="@drawable/splash"
|
||||||
|
android:gravity="center" />
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
|
@ -1,8 +0,0 @@
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
|
||||||
<item android:drawable="@color/splashBackground"/>
|
|
||||||
<item>
|
|
||||||
<bitmap
|
|
||||||
android:src="@drawable/launch_screen"
|
|
||||||
android:gravity="center"/>
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/splashBackground"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:src="@drawable/launch_screen"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
/>
|
|
||||||
</RelativeLayout>
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<item name="splashBackground" type="color">#000000</item>
|
||||||
|
</resources>
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="primary_dark">#660B0B0B</color>
|
<color name="primary_dark">#660B0B0B</color>
|
||||||
<item name="splashBackground" type="color">#FFFFFF</item>
|
<item name="splashBackground" type="color">#eeeff1</item>
|
||||||
<item name="notification_text" type="color">#CC3333</item>
|
<item name="notification_text" type="color">#CC3333</item>
|
||||||
</resources>
|
</resources>
|
|
@ -1,7 +1,8 @@
|
||||||
<resources>
|
<resources>
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
<item name="android:colorEdgeEffect">#aaaaaa</item>
|
<item name="android:colorEdgeEffect">#aaaaaa</item>
|
||||||
<item name="android:textColor">#000000</item>
|
<item name="colorPrimaryDark">@color/splashBackground</item>
|
||||||
|
<item name="android:navigationBarColor">@color/splashBackground</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Share.Window" parent="android:Theme">
|
<style name="Share.Window" parent="android:Theme">
|
||||||
|
@ -18,4 +19,10 @@
|
||||||
<item name="android:backgroundDimEnabled">true</item>
|
<item name="android:backgroundDimEnabled">true</item>
|
||||||
<item name="android:windowAnimationStyle">@style/Share.Window</item>
|
<item name="android:windowAnimationStyle">@style/Share.Window</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="BootTheme" parent="AppTheme">
|
||||||
|
<item name="android:background">@drawable/launch_screen</item>
|
||||||
|
<item name="colorPrimaryDark">@color/splashBackground</item>
|
||||||
|
<item name="android:navigationBarColor">@color/splashBackground</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -55,16 +55,16 @@ allprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// subprojects { subproject ->
|
subprojects { subproject ->
|
||||||
// afterEvaluate {
|
afterEvaluate {
|
||||||
// if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
|
if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
|
||||||
// android {
|
android {
|
||||||
// compileSdkVersion 28
|
compileSdkVersion 28
|
||||||
// buildToolsVersion "28.0.3"
|
buildToolsVersion "28.0.3"
|
||||||
// defaultConfig {
|
defaultConfig {
|
||||||
// targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
|
@ -54,3 +54,11 @@ export const TOGGLE_CRASH_REPORT = 'TOGGLE_CRASH_REPORT';
|
||||||
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
|
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
|
||||||
export const SET_ACTIVE_USERS = 'SET_ACTIVE_USERS';
|
export const SET_ACTIVE_USERS = 'SET_ACTIVE_USERS';
|
||||||
export const USERS_TYPING = createRequestTypes('USERS_TYPING', ['ADD', 'REMOVE', 'CLEAR']);
|
export const USERS_TYPING = createRequestTypes('USERS_TYPING', ['ADD', 'REMOVE', 'CLEAR']);
|
||||||
|
export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [
|
||||||
|
'SET_TOKEN',
|
||||||
|
'SET_PARAMS',
|
||||||
|
'SET_INVITE',
|
||||||
|
'CREATE',
|
||||||
|
'CLEAR',
|
||||||
|
...defaultTypes
|
||||||
|
]);
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
|
export function inviteLinksSetToken(token) {
|
||||||
|
return {
|
||||||
|
type: types.INVITE_LINKS.SET_TOKEN,
|
||||||
|
token
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inviteLinksRequest(token) {
|
||||||
|
return {
|
||||||
|
type: types.INVITE_LINKS.REQUEST,
|
||||||
|
token
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inviteLinksSuccess() {
|
||||||
|
return {
|
||||||
|
type: types.INVITE_LINKS.SUCCESS
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inviteLinksFailure() {
|
||||||
|
return {
|
||||||
|
type: types.INVITE_LINKS.FAILURE
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inviteLinksClear() {
|
||||||
|
return {
|
||||||
|
type: types.INVITE_LINKS.CLEAR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function inviteLinksCreate(rid) {
|
||||||
|
return {
|
||||||
|
type: types.INVITE_LINKS.CREATE,
|
||||||
|
rid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inviteLinksSetParams(params) {
|
||||||
|
return {
|
||||||
|
type: types.INVITE_LINKS.SET_PARAMS,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inviteLinksSetInvite(invite) {
|
||||||
|
return {
|
||||||
|
type: types.INVITE_LINKS.SET_INVITE,
|
||||||
|
invite
|
||||||
|
};
|
||||||
|
}
|
|
@ -59,6 +59,9 @@ export default {
|
||||||
Message_TimeFormat: {
|
Message_TimeFormat: {
|
||||||
type: 'valueAsString'
|
type: 'valueAsString'
|
||||||
},
|
},
|
||||||
|
Message_TimeAndDateFormat: {
|
||||||
|
type: 'valueAsString'
|
||||||
|
},
|
||||||
Site_Name: {
|
Site_Name: {
|
||||||
type: 'valueAsString'
|
type: 'valueAsString'
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Text, TouchableOpacity, FlatList } from 'react-native';
|
import { Text, TouchableOpacity, FlatList } from 'react-native';
|
||||||
import { shortnameToUnicode } from 'emoji-toolkit';
|
|
||||||
import { responsive } from 'react-native-responsive-ui';
|
import { responsive } from 'react-native-responsive-ui';
|
||||||
|
|
||||||
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import CustomEmoji from './CustomEmoji';
|
import CustomEmoji from './CustomEmoji';
|
||||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React, { Component } from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||||
import { shortnameToUnicode } from 'emoji-toolkit';
|
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
|
@ -15,6 +14,7 @@ import categories from './categories';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import { emojisByCategory } from '../../emojis';
|
import { emojisByCategory } from '../../emojis';
|
||||||
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
||||||
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import {
|
|
||||||
View, Text, TouchableWithoutFeedback, StyleSheet, SafeAreaView
|
|
||||||
} from 'react-native';
|
|
||||||
import FastImage from 'react-native-fast-image';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Modal from 'react-native-modal';
|
|
||||||
import ImageViewer from 'react-native-image-zoom-viewer';
|
|
||||||
import { Video } from 'expo-av';
|
|
||||||
|
|
||||||
import sharedStyles from '../views/Styles';
|
|
||||||
import { formatAttachmentUrl } from '../lib/utils';
|
|
||||||
import ActivityIndicator from './ActivityIndicator';
|
|
||||||
import { themes } from '../constants/colors';
|
|
||||||
import { withTheme } from '../theme';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
safeArea: {
|
|
||||||
flex: 1
|
|
||||||
},
|
|
||||||
modal: {
|
|
||||||
margin: 0
|
|
||||||
},
|
|
||||||
titleContainer: {
|
|
||||||
width: '100%',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginVertical: 10
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: 16,
|
|
||||||
...sharedStyles.textSemibold
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: 14,
|
|
||||||
...sharedStyles.textMedium
|
|
||||||
},
|
|
||||||
video: {
|
|
||||||
flex: 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ModalContent = React.memo(({
|
|
||||||
attachment, onClose, user, baseUrl, theme
|
|
||||||
}) => {
|
|
||||||
if (attachment && attachment.image_url) {
|
|
||||||
const url = formatAttachmentUrl(attachment.image_url, user.id, user.token, baseUrl);
|
|
||||||
return (
|
|
||||||
<SafeAreaView style={styles.safeArea}>
|
|
||||||
<TouchableWithoutFeedback onPress={onClose}>
|
|
||||||
<View style={styles.titleContainer}>
|
|
||||||
<Text style={[styles.title, { color: themes[theme].buttonText }]}>{attachment.title}</Text>
|
|
||||||
{attachment.description ? <Text style={[styles.description, { color: themes[theme].buttonText }]}>{attachment.description}</Text> : null}
|
|
||||||
</View>
|
|
||||||
</TouchableWithoutFeedback>
|
|
||||||
<ImageViewer
|
|
||||||
imageUrls={[{ url }]}
|
|
||||||
onClick={onClose}
|
|
||||||
backgroundColor='transparent'
|
|
||||||
enableSwipeDown
|
|
||||||
onSwipeDown={onClose}
|
|
||||||
renderIndicator={() => null}
|
|
||||||
renderImage={props => <FastImage {...props} />}
|
|
||||||
loadingRender={() => <ActivityIndicator size='large' theme={theme} />}
|
|
||||||
/>
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (attachment && attachment.video_url) {
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const uri = formatAttachmentUrl(attachment.video_url, user.id, user.token, baseUrl);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Video
|
|
||||||
source={{ uri }}
|
|
||||||
rate={1.0}
|
|
||||||
volume={1.0}
|
|
||||||
isMuted={false}
|
|
||||||
resizeMode={Video.RESIZE_MODE_CONTAIN}
|
|
||||||
shouldPlay
|
|
||||||
isLooping={false}
|
|
||||||
style={styles.video}
|
|
||||||
useNativeControls
|
|
||||||
onReadyForDisplay={() => setLoading(false)}
|
|
||||||
onLoadStart={() => setLoading(true)}
|
|
||||||
onError={console.log}
|
|
||||||
/>
|
|
||||||
{ loading ? <ActivityIndicator size='large' theme={theme} absolute /> : null }
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const FileModal = React.memo(({
|
|
||||||
isVisible, onClose, attachment, user, baseUrl, theme
|
|
||||||
}) => (
|
|
||||||
<Modal
|
|
||||||
style={styles.modal}
|
|
||||||
isVisible={isVisible}
|
|
||||||
onBackdropPress={onClose}
|
|
||||||
onBackButtonPress={onClose}
|
|
||||||
onSwipeComplete={onClose}
|
|
||||||
swipeDirection={['up', 'down']}
|
|
||||||
>
|
|
||||||
<ModalContent attachment={attachment} onClose={onClose} user={user} baseUrl={baseUrl} theme={theme} />
|
|
||||||
</Modal>
|
|
||||||
), (prevProps, nextProps) => (
|
|
||||||
prevProps.isVisible === nextProps.isVisible && prevProps.loading === nextProps.loading && prevProps.theme === nextProps.theme
|
|
||||||
));
|
|
||||||
|
|
||||||
FileModal.propTypes = {
|
|
||||||
isVisible: PropTypes.bool,
|
|
||||||
attachment: PropTypes.object,
|
|
||||||
user: PropTypes.object,
|
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
theme: PropTypes.string,
|
|
||||||
onClose: PropTypes.func
|
|
||||||
};
|
|
||||||
FileModal.displayName = 'FileModal';
|
|
||||||
|
|
||||||
ModalContent.propTypes = {
|
|
||||||
attachment: PropTypes.object,
|
|
||||||
user: PropTypes.object,
|
|
||||||
baseUrl: PropTypes.string,
|
|
||||||
theme: PropTypes.string,
|
|
||||||
onClose: PropTypes.func
|
|
||||||
};
|
|
||||||
ModalContent.displayName = 'FileModalContent';
|
|
||||||
|
|
||||||
export default withTheme(FileModal);
|
|
|
@ -57,6 +57,12 @@ export const MoreButton = React.memo(({ onPress, testID }) => (
|
||||||
</CustomHeaderButtons>
|
</CustomHeaderButtons>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
export const SaveButton = React.memo(({ onPress, testID }) => (
|
||||||
|
<CustomHeaderButtons>
|
||||||
|
<Item title='save' iconName='Download' onPress={onPress} testID={testID} />
|
||||||
|
</CustomHeaderButtons>
|
||||||
|
));
|
||||||
|
|
||||||
export const LegalButton = React.memo(({ navigation, testID }) => (
|
export const LegalButton = React.memo(({ navigation, testID }) => (
|
||||||
<MoreButton onPress={() => navigation.navigate('LegalView')} testID={testID} />
|
<MoreButton onPress={() => navigation.navigate('LegalView')} testID={testID} />
|
||||||
));
|
));
|
||||||
|
@ -80,6 +86,10 @@ MoreButton.propTypes = {
|
||||||
onPress: PropTypes.func.isRequired,
|
onPress: PropTypes.func.isRequired,
|
||||||
testID: PropTypes.string.isRequired
|
testID: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
SaveButton.propTypes = {
|
||||||
|
onPress: PropTypes.func.isRequired,
|
||||||
|
testID: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
LegalButton.propTypes = {
|
LegalButton.propTypes = {
|
||||||
navigation: PropTypes.object.isRequired,
|
navigation: PropTypes.object.isRequired,
|
||||||
testID: PropTypes.string.isRequired
|
testID: PropTypes.string.isRequired
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { shortnameToUnicode } from 'emoji-toolkit';
|
|
||||||
|
|
||||||
|
import shortnameToUnicode from '../../../utils/shortnameToUnicode';
|
||||||
import styles from '../styles';
|
import styles from '../styles';
|
||||||
import MessageboxContext from '../Context';
|
import MessageboxContext from '../Context';
|
||||||
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
||||||
|
|
|
@ -42,7 +42,6 @@ import {
|
||||||
MENTIONS_TRACKING_TYPE_USERS
|
MENTIONS_TRACKING_TYPE_USERS
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import CommandsPreview from './CommandsPreview';
|
import CommandsPreview from './CommandsPreview';
|
||||||
import { withTheme } from '../../theme';
|
|
||||||
|
|
||||||
const imagePickerConfig = {
|
const imagePickerConfig = {
|
||||||
cropping: true,
|
cropping: true,
|
||||||
|
@ -566,7 +565,6 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
showUploadModal = (file) => {
|
showUploadModal = (file) => {
|
||||||
this.setState({ file: { ...file, isVisible: true } });
|
this.setState({ file: { ...file, isVisible: true } });
|
||||||
}
|
}
|
||||||
|
@ -889,4 +887,4 @@ const dispatchToProps = ({
|
||||||
typing: (rid, status) => userTypingAction(rid, status)
|
typing: (rid, status) => userTypingAction(rid, status)
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withTheme(MessageBox));
|
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(MessageBox);
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { STATUS_COLORS } from '../../constants/colors';
|
import { STATUS_COLORS, themes } from '../../constants/colors';
|
||||||
|
|
||||||
const Status = React.memo(({ status, size, style }) => (
|
const Status = React.memo(({
|
||||||
|
status, size, style, theme
|
||||||
|
}) => (
|
||||||
<View
|
<View
|
||||||
style={
|
style={
|
||||||
[
|
[
|
||||||
|
@ -12,7 +14,8 @@ const Status = React.memo(({ status, size, style }) => (
|
||||||
borderRadius: size,
|
borderRadius: size,
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
backgroundColor: STATUS_COLORS[status]
|
backgroundColor: STATUS_COLORS[status],
|
||||||
|
borderColor: themes[theme].backgroundColor
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
@ -20,11 +23,13 @@ const Status = React.memo(({ status, size, style }) => (
|
||||||
Status.propTypes = {
|
Status.propTypes = {
|
||||||
status: PropTypes.string,
|
status: PropTypes.string,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
style: PropTypes.any
|
style: PropTypes.any,
|
||||||
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
Status.defaultProps = {
|
Status.defaultProps = {
|
||||||
status: 'offline',
|
status: 'offline',
|
||||||
size: 16
|
size: 16,
|
||||||
|
theme: 'light'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Status;
|
export default Status;
|
||||||
|
|
|
@ -3,12 +3,14 @@ import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import Status from './Status';
|
import Status from './Status';
|
||||||
|
import { withTheme } from '../../theme';
|
||||||
|
|
||||||
class StatusContainer extends React.PureComponent {
|
class StatusContainer extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
style: PropTypes.any,
|
style: PropTypes.any,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
status: PropTypes.string
|
status: PropTypes.string,
|
||||||
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -16,8 +18,10 @@ class StatusContainer extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { style, size, status } = this.props;
|
const {
|
||||||
return <Status size={size} style={style} status={status} />;
|
style, size, status, theme
|
||||||
|
} = this.props;
|
||||||
|
return <Status size={size} style={style} status={status} theme={theme} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,4 +29,4 @@ const mapStateToProps = (state, ownProps) => ({
|
||||||
status: state.meteor.connected ? state.activeUsers[ownProps.id] : 'offline'
|
status: state.meteor.connected ? state.activeUsers[ownProps.id] : 'offline'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(StatusContainer);
|
export default connect(mapStateToProps)(withTheme(StatusContainer));
|
||||||
|
|
|
@ -40,7 +40,7 @@ const AtMention = React.memo(({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
style={[preview ? { ...styles.text, color: themes[theme].titleText } : mentionStyle, ...style]}
|
style={[preview ? { ...styles.text, color: themes[theme].bodyText } : mentionStyle, ...style]}
|
||||||
onPress={preview ? undefined : handlePress}
|
onPress={preview ? undefined : handlePress}
|
||||||
>
|
>
|
||||||
{`@${ mention }`}
|
{`@${ mention }`}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
import { shortnameToUnicode } from 'emoji-toolkit';
|
|
||||||
|
|
||||||
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ const Emoji = React.memo(({
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
{ color: themes[theme].titleText },
|
{ color: themes[theme].bodyText },
|
||||||
isMessageContainsOnlyEmoji ? styles.textBig : styles.text,
|
isMessageContainsOnlyEmoji ? styles.textBig : styles.text,
|
||||||
...style
|
...style
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -21,7 +21,7 @@ const Hashtag = React.memo(({
|
||||||
if (channels && channels.length && channels.findIndex(channel => channel.name === hashtag) !== -1) {
|
if (channels && channels.length && channels.findIndex(channel => channel.name === hashtag) !== -1) {
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
style={[preview ? { ...styles.text, color: themes[theme].titleText } : styles.mention, ...style]}
|
style={[preview ? { ...styles.text, color: themes[theme].bodyText } : styles.mention, ...style]}
|
||||||
onPress={preview ? undefined : handlePress}
|
onPress={preview ? undefined : handlePress}
|
||||||
>
|
>
|
||||||
{`#${ hashtag }`}
|
{`#${ hashtag }`}
|
||||||
|
@ -29,7 +29,7 @@ const Hashtag = React.memo(({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Text style={[preview ? { ...styles.text, color: themes[theme].titleText } : styles.mention, ...style]}>
|
<Text style={[preview ? { ...styles.text, color: themes[theme].bodyText } : styles.mention, ...style]}>
|
||||||
{`#${ hashtag }`}
|
{`#${ hashtag }`}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,7 +25,7 @@ const Link = React.memo(({
|
||||||
style={
|
style={
|
||||||
!preview
|
!preview
|
||||||
? { ...styles.link, color: themes[theme].actionTintColor }
|
? { ...styles.link, color: themes[theme].actionTintColor }
|
||||||
: { color: themes[theme].titleText }
|
: { color: themes[theme].bodyText }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{ childLength !== 0 ? children : link }
|
{ childLength !== 0 ? children : link }
|
||||||
|
|
|
@ -39,7 +39,7 @@ const ListItem = React.memo(({
|
||||||
return (
|
return (
|
||||||
<View style={style.container}>
|
<View style={style.container}>
|
||||||
<View style={[{ width: bulletWidth }, style.bullet]}>
|
<View style={[{ width: bulletWidth }, style.bullet]}>
|
||||||
<Text style={{ color: themes[theme].titleText }}>
|
<Text style={{ color: themes[theme].bodyText }}>
|
||||||
{bullet}
|
{bullet}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -25,7 +25,7 @@ const TableCell = React.memo(({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[...cellStyle, { width: CELL_WIDTH }]}>
|
<View style={[...cellStyle, { width: CELL_WIDTH }]}>
|
||||||
<Text style={[textStyle, { color: themes[theme].titleText }]}>
|
<Text style={[textStyle, { color: themes[theme].bodyText }]}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { Text, Image } from 'react-native';
|
||||||
import { Parser, Node } from 'commonmark';
|
import { Parser, Node } from 'commonmark';
|
||||||
import Renderer from 'commonmark-react-renderer';
|
import Renderer from 'commonmark-react-renderer';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { shortnameToUnicode } from 'emoji-toolkit';
|
|
||||||
|
|
||||||
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
|
||||||
|
@ -171,11 +171,11 @@ class Markdown extends PureComponent {
|
||||||
!preview
|
!preview
|
||||||
? {
|
? {
|
||||||
...styles.codeInline,
|
...styles.codeInline,
|
||||||
color: themes[theme].titleText,
|
color: themes[theme].bodyText,
|
||||||
backgroundColor: themes[theme].bannerBackground,
|
backgroundColor: themes[theme].bannerBackground,
|
||||||
borderColor: themes[theme].bannerBackground
|
borderColor: themes[theme].bannerBackground
|
||||||
}
|
}
|
||||||
: { ...styles.text, color: themes[theme].titleText },
|
: { ...styles.text, color: themes[theme].bodyText },
|
||||||
...style
|
...style
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
@ -192,11 +192,11 @@ class Markdown extends PureComponent {
|
||||||
!preview
|
!preview
|
||||||
? {
|
? {
|
||||||
...styles.codeBlock,
|
...styles.codeBlock,
|
||||||
color: themes[theme].titleText,
|
color: themes[theme].bodyText,
|
||||||
backgroundColor: themes[theme].bannerBackground,
|
backgroundColor: themes[theme].bannerBackground,
|
||||||
borderColor: themes[theme].bannerBackground
|
borderColor: themes[theme].bannerBackground
|
||||||
}
|
}
|
||||||
: { ...styles.text, color: themes[theme].titleText },
|
: { ...styles.text, color: themes[theme].bodyText },
|
||||||
...style
|
...style
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
@ -216,7 +216,7 @@ class Markdown extends PureComponent {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Text style={[style, { color: themes[theme].titleText }]} numberOfLines={numberOfLines}>
|
<Text style={[style, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
@ -297,7 +297,7 @@ class Markdown extends PureComponent {
|
||||||
const { numberOfLines, theme } = this.props;
|
const { numberOfLines, theme } = this.props;
|
||||||
const textStyle = styles[`heading${ level }Text`];
|
const textStyle = styles[`heading${ level }Text`];
|
||||||
return (
|
return (
|
||||||
<Text numberOfLines={numberOfLines} style={[textStyle, { color: themes[theme].titleText }]}>
|
<Text numberOfLines={numberOfLines} style={[textStyle, { color: themes[theme].bodyText }]}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
@ -390,7 +390,7 @@ class Markdown extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useMarkdown && !preview) {
|
if (!useMarkdown && !preview) {
|
||||||
return <Text style={[styles.text, { color: themes[theme].titleText }]} numberOfLines={numberOfLines}>{m}</Text>;
|
return <Text style={[styles.text, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines}>{m}</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ast = this.parser.parse(m);
|
const ast = this.parser.parse(m);
|
||||||
|
|
|
@ -8,7 +8,7 @@ import Video from './Video';
|
||||||
import Reply from './Reply';
|
import Reply from './Reply';
|
||||||
|
|
||||||
const Attachments = React.memo(({
|
const Attachments = React.memo(({
|
||||||
attachments, timeFormat, user, baseUrl, useMarkdown, onOpenFileModal, getCustomEmoji, theme
|
attachments, timeFormat, user, baseUrl, useMarkdown, showAttachment, getCustomEmoji, theme
|
||||||
}) => {
|
}) => {
|
||||||
if (!attachments || attachments.length === 0) {
|
if (!attachments || attachments.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -16,13 +16,13 @@ const Attachments = React.memo(({
|
||||||
|
|
||||||
return attachments.map((file, index) => {
|
return attachments.map((file, index) => {
|
||||||
if (file.image_url) {
|
if (file.image_url) {
|
||||||
return <Image key={file.image_url} file={file} user={user} baseUrl={baseUrl} onOpenFileModal={onOpenFileModal} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
|
return <Image key={file.image_url} file={file} user={user} baseUrl={baseUrl} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
|
||||||
}
|
}
|
||||||
if (file.audio_url) {
|
if (file.audio_url) {
|
||||||
return <Audio key={file.audio_url} file={file} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
|
return <Audio key={file.audio_url} file={file} user={user} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
|
||||||
}
|
}
|
||||||
if (file.video_url) {
|
if (file.video_url) {
|
||||||
return <Video key={file.video_url} file={file} user={user} baseUrl={baseUrl} onOpenFileModal={onOpenFileModal} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
|
return <Video key={file.video_url} file={file} user={user} baseUrl={baseUrl} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} useMarkdown={useMarkdown} theme={theme} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
@ -36,7 +36,7 @@ Attachments.propTypes = {
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
useMarkdown: PropTypes.bool,
|
useMarkdown: PropTypes.bool,
|
||||||
onOpenFileModal: PropTypes.func,
|
showAttachment: PropTypes.func,
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,7 +16,7 @@ const Content = React.memo((props) => {
|
||||||
let content = null;
|
let content = null;
|
||||||
|
|
||||||
if (props.tmid && !props.msg) {
|
if (props.tmid && !props.msg) {
|
||||||
content = <Text style={[styles.text, { color: themes[props.theme].titleText }]}>{I18n.t('Sent_an_attachment')}</Text>;
|
content = <Text style={[styles.text, { color: themes[props.theme].bodyText }]}>{I18n.t('Sent_an_attachment')}</Text>;
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<Markdown
|
<Markdown
|
||||||
|
|
|
@ -18,7 +18,7 @@ const Discussion = React.memo(({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Text style={[styles.startedDiscussion, { color: themes[theme].auxiliaryText }]}>{I18n.t('Started_discussion')}</Text>
|
<Text style={[styles.startedDiscussion, { color: themes[theme].auxiliaryText }]}>{I18n.t('Started_discussion')}</Text>
|
||||||
<Text style={[styles.text, { color: themes[theme].titleText }]}>{msg}</Text>
|
<Text style={[styles.text, { color: themes[theme].bodyText }]}>{msg}</Text>
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={onDiscussionPress}
|
onPress={onDiscussionPress}
|
||||||
|
@ -28,7 +28,7 @@ const Discussion = React.memo(({
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<CustomIcon name='chat' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
|
<CustomIcon name='chat' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} />
|
||||||
<Text style={[styles.buttonText, { color: themes[theme].titleText }]}>{buttonText}</Text>
|
<Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{buttonText}</Text>
|
||||||
</>
|
</>
|
||||||
</Touchable>
|
</Touchable>
|
||||||
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text } from 'react-native';
|
import { Text } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { shortnameToUnicode } from 'emoji-toolkit';
|
|
||||||
|
|
||||||
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
|
|
||||||
const Emoji = React.memo(({
|
const Emoji = React.memo(({
|
||||||
|
|
|
@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
import { createImageProgress } from 'react-native-image-progress';
|
||||||
|
import * as Progress from 'react-native-progress';
|
||||||
|
|
||||||
import Markdown from '../markdown';
|
import Markdown from '../markdown';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -12,6 +14,8 @@ import { withSplit } from '../../split';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
|
|
||||||
|
const ImageProgress = createImageProgress(FastImage);
|
||||||
|
|
||||||
const Button = React.memo(({
|
const Button = React.memo(({
|
||||||
children, onPress, split, theme
|
children, onPress, split, theme
|
||||||
}) => (
|
}) => (
|
||||||
|
@ -25,22 +29,26 @@ const Button = React.memo(({
|
||||||
));
|
));
|
||||||
|
|
||||||
const Image = React.memo(({ img, theme }) => (
|
const Image = React.memo(({ img, theme }) => (
|
||||||
<FastImage
|
<ImageProgress
|
||||||
style={[styles.image, { borderColor: themes[theme].borderColor }]}
|
style={[styles.image, { borderColor: themes[theme].borderColor }]}
|
||||||
source={{ uri: encodeURI(img) }}
|
source={{ uri: encodeURI(img) }}
|
||||||
resizeMode={FastImage.resizeMode.cover}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
|
indicator={Progress.Pie}
|
||||||
|
indicatorProps={{
|
||||||
|
color: themes[theme].actionTintColor
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
const ImageContainer = React.memo(({
|
const ImageContainer = React.memo(({
|
||||||
file, baseUrl, user, useMarkdown, onOpenFileModal, getCustomEmoji, split, theme
|
file, baseUrl, user, useMarkdown, showAttachment, getCustomEmoji, split, theme
|
||||||
}) => {
|
}) => {
|
||||||
const img = formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
|
const img = formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl);
|
||||||
if (!img) {
|
if (!img) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPress = () => onOpenFileModal(file);
|
const onPress = () => showAttachment(file);
|
||||||
|
|
||||||
if (file.description) {
|
if (file.description) {
|
||||||
return (
|
return (
|
||||||
|
@ -65,7 +73,7 @@ ImageContainer.propTypes = {
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
useMarkdown: PropTypes.bool,
|
useMarkdown: PropTypes.bool,
|
||||||
onOpenFileModal: PropTypes.func,
|
showAttachment: PropTypes.func,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
split: PropTypes.bool
|
split: PropTypes.bool
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import removeMarkdown from 'remove-markdown';
|
import removeMarkdown from 'remove-markdown';
|
||||||
import { shortnameToUnicode } from 'emoji-toolkit';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import DisclosureIndicator from '../DisclosureIndicator';
|
import DisclosureIndicator from '../DisclosureIndicator';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
|
@ -72,7 +72,7 @@ const Title = React.memo(({ attachment, timeFormat, theme }) => {
|
||||||
const time = attachment.ts ? moment(attachment.ts).format(timeFormat) : null;
|
const time = attachment.ts ? moment(attachment.ts).format(timeFormat) : null;
|
||||||
return (
|
return (
|
||||||
<View style={styles.authorContainer}>
|
<View style={styles.authorContainer}>
|
||||||
{attachment.author_name ? <Text style={[styles.author, { color: themes[theme].titleText }]}>{attachment.author_name}</Text> : null}
|
{attachment.author_name ? <Text style={[styles.author, { color: themes[theme].bodyText }]}>{attachment.author_name}</Text> : null}
|
||||||
{time ? <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{ time }</Text> : null}
|
{time ? <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{ time }</Text> : null}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -116,8 +116,8 @@ const Fields = React.memo(({ attachment, theme }) => {
|
||||||
<View style={styles.fieldsContainer}>
|
<View style={styles.fieldsContainer}>
|
||||||
{attachment.fields.map(field => (
|
{attachment.fields.map(field => (
|
||||||
<View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
|
<View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}>
|
||||||
<Text style={[styles.fieldTitle, { color: themes[theme].titleText }]}>{field.title}</Text>
|
<Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text>
|
||||||
<Text style={[styles.fieldValue, { color: themes[theme].titleText }]}>{field.value}</Text>
|
<Text style={[styles.fieldValue, { color: themes[theme].bodyText }]}>{field.value}</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -27,7 +27,7 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
const Video = React.memo(({
|
const Video = React.memo(({
|
||||||
file, baseUrl, user, useMarkdown, onOpenFileModal, getCustomEmoji, theme
|
file, baseUrl, user, useMarkdown, showAttachment, getCustomEmoji, theme
|
||||||
}) => {
|
}) => {
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -35,7 +35,7 @@ const Video = React.memo(({
|
||||||
|
|
||||||
const onPress = () => {
|
const onPress = () => {
|
||||||
if (isTypeSupported(file.video_type)) {
|
if (isTypeSupported(file.video_type)) {
|
||||||
return onOpenFileModal(file);
|
return showAttachment(file);
|
||||||
}
|
}
|
||||||
const uri = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
|
const uri = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
|
||||||
openLink(uri, theme);
|
openLink(uri, theme);
|
||||||
|
@ -64,7 +64,7 @@ Video.propTypes = {
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
useMarkdown: PropTypes.bool,
|
useMarkdown: PropTypes.bool,
|
||||||
onOpenFileModal: PropTypes.func,
|
showAttachment: PropTypes.func,
|
||||||
getCustomEmoji: PropTypes.func,
|
getCustomEmoji: PropTypes.func,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,7 +40,7 @@ class MessageContainer extends React.Component {
|
||||||
replyBroadcast: PropTypes.func,
|
replyBroadcast: PropTypes.func,
|
||||||
reactionInit: PropTypes.func,
|
reactionInit: PropTypes.func,
|
||||||
fetchThreadName: PropTypes.func,
|
fetchThreadName: PropTypes.func,
|
||||||
onOpenFileModal: PropTypes.func,
|
showAttachment: PropTypes.func,
|
||||||
onReactionLongPress: PropTypes.func,
|
onReactionLongPress: PropTypes.func,
|
||||||
navToRoomInfo: PropTypes.func,
|
navToRoomInfo: PropTypes.func,
|
||||||
callJitsi: PropTypes.func,
|
callJitsi: PropTypes.func,
|
||||||
|
@ -212,7 +212,7 @@ class MessageContainer extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi, theme
|
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, showAttachment, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi, theme
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, autoTranslate: autoTranslateMessage
|
id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, autoTranslate: autoTranslateMessage
|
||||||
|
@ -275,7 +275,7 @@ class MessageContainer extends React.Component {
|
||||||
replyBroadcast={this.replyBroadcast}
|
replyBroadcast={this.replyBroadcast}
|
||||||
reactionInit={this.reactionInit}
|
reactionInit={this.reactionInit}
|
||||||
onDiscussionPress={this.onDiscussionPress}
|
onDiscussionPress={this.onDiscussionPress}
|
||||||
onOpenFileModal={onOpenFileModal}
|
showAttachment={showAttachment}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
navToRoomInfo={navToRoomInfo}
|
navToRoomInfo={navToRoomInfo}
|
||||||
callJitsi={callJitsi}
|
callJitsi={callJitsi}
|
||||||
|
|
|
@ -16,6 +16,7 @@ export default {
|
||||||
'error-duplicate-channel-name': 'Ein Kanal mit dem Namen {{channel_name}} ist bereits vorhanden',
|
'error-duplicate-channel-name': 'Ein Kanal mit dem Namen {{channel_name}} ist bereits vorhanden',
|
||||||
'error-email-domain-blacklisted': 'Die E-Mail-Domain wird auf die schwarze Liste gesetzt',
|
'error-email-domain-blacklisted': 'Die E-Mail-Domain wird auf die schwarze Liste gesetzt',
|
||||||
'error-email-send-failed': 'Fehler beim Versuch, eine E-Mail zu senden: {{message}}',
|
'error-email-send-failed': 'Fehler beim Versuch, eine E-Mail zu senden: {{message}}',
|
||||||
|
'error-save-image': 'Fehler beim Speichern des Bildes',
|
||||||
'error-field-unavailable': '{{field}} wird bereits verwendet :(',
|
'error-field-unavailable': '{{field}} wird bereits verwendet :(',
|
||||||
'error-file-too-large': 'Datei ist zu groß',
|
'error-file-too-large': 'Datei ist zu groß',
|
||||||
'error-importer-not-defined': 'Der Import wurde nicht korrekt definiert, es fehlt die Importklasse.',
|
'error-importer-not-defined': 'Der Import wurde nicht korrekt definiert, es fehlt die Importklasse.',
|
||||||
|
@ -33,7 +34,7 @@ export default {
|
||||||
'error-invalid-email': 'Ungültige E-Mail {{emai}}',
|
'error-invalid-email': 'Ungültige E-Mail {{emai}}',
|
||||||
'error-invalid-email-address': 'Ungültige E-Mail-Adresse',
|
'error-invalid-email-address': 'Ungültige E-Mail-Adresse',
|
||||||
'error-invalid-file-height': 'Ungültige Dateihöhe',
|
'error-invalid-file-height': 'Ungültige Dateihöhe',
|
||||||
'error-invalid-file-type': 'ungültiger Dateityp',
|
'error-invalid-file-type': 'Ungültiger Dateityp',
|
||||||
'error-invalid-file-width': 'Ungültige Dateibreite',
|
'error-invalid-file-width': 'Ungültige Dateibreite',
|
||||||
'error-invalid-from-address': 'Sie haben eine ungültige FROM-Adresse mitgeteilt.',
|
'error-invalid-from-address': 'Sie haben eine ungültige FROM-Adresse mitgeteilt.',
|
||||||
'error-invalid-integration': 'Ungültige Integration',
|
'error-invalid-integration': 'Ungültige Integration',
|
||||||
|
@ -80,29 +81,36 @@ export default {
|
||||||
Activity: 'Aktivität',
|
Activity: 'Aktivität',
|
||||||
Add_Reaction: 'Reaktion hinzufügen',
|
Add_Reaction: 'Reaktion hinzufügen',
|
||||||
Add_Server: 'Server hinzufügen',
|
Add_Server: 'Server hinzufügen',
|
||||||
Add_user: 'Nutzer hinzufügen',
|
Add_users: 'Nutzer hinzufügen',
|
||||||
Admin_Panel: 'Admin Panel',
|
Admin_Panel: 'Admin Panel',
|
||||||
Alert: 'Warnen',
|
Alert: 'Warnen',
|
||||||
alert: 'warnen',
|
alert: 'warnen',
|
||||||
alerts: 'Warnungen',
|
alerts: 'Warnungen',
|
||||||
All_users_in_the_channel_can_write_new_messages: 'Alle Benutzer im Kanal können neue Nachrichten schreiben',
|
All_users_in_the_channel_can_write_new_messages: 'Alle Benutzer im Kanal können neue Nachrichten schreiben',
|
||||||
All: 'Alles',
|
All: 'Alles',
|
||||||
|
All_Messages: 'Alle Nachrichten',
|
||||||
Allow_Reactions: 'Reaktionen zulassen',
|
Allow_Reactions: 'Reaktionen zulassen',
|
||||||
Alphabetical: 'Alphabetisch',
|
Alphabetical: 'Alphabetisch',
|
||||||
and_more: 'und mehr',
|
and_more: 'und mehr',
|
||||||
and: 'und',
|
and: 'und',
|
||||||
announcement: 'Ankündigung',
|
announcement: 'Ankündigung',
|
||||||
Announcement: 'Ankündigung',
|
Announcement: 'Ankündigung',
|
||||||
|
Apply_Your_Certificate: 'Wenden Sie Ihr Zertifikat an',
|
||||||
|
Applying_a_theme_will_change_how_the_app_looks: 'Ein Theme zu setzen wird das Aussehen der Anwendung ändern.',
|
||||||
ARCHIVE: 'ARCHIV',
|
ARCHIVE: 'ARCHIV',
|
||||||
archive: 'Archiv',
|
archive: 'Archiv',
|
||||||
are_typing: 'tippen',
|
are_typing: 'tippen',
|
||||||
Are_you_sure_question_mark: 'Sind Sie sicher?',
|
Are_you_sure_question_mark: 'Sind Sie sicher?',
|
||||||
Are_you_sure_you_want_to_leave_the_room: 'Möchten Sie den Raum wirklich verlassen {{room}}?',
|
Are_you_sure_you_want_to_leave_the_room: 'Möchten Sie den Raum wirklich verlassen {{room}}?',
|
||||||
|
Audio: 'Audio',
|
||||||
Authenticating: 'Authentifizierung',
|
Authenticating: 'Authentifizierung',
|
||||||
|
Automatic: 'Automatisch',
|
||||||
Auto_Translate: 'Automatische Übersetzung',
|
Auto_Translate: 'Automatische Übersetzung',
|
||||||
Avatar_changed_successfully: 'Avatar erfolgreich geändert!',
|
Avatar_changed_successfully: 'Avatar erfolgreich geändert!',
|
||||||
Avatar_Url: 'Avatar-URL',
|
Avatar_Url: 'Avatar-URL',
|
||||||
Away: 'Abwesend',
|
Away: 'Abwesend',
|
||||||
|
Back: 'Zurück',
|
||||||
|
Black: 'Schwarz',
|
||||||
Block_user: 'Benutzer blockieren',
|
Block_user: 'Benutzer blockieren',
|
||||||
Broadcast_channel_Description: 'Nur autorisierte Benutzer können neue Nachrichten schreiben, die anderen Benutzer können jedoch antworten',
|
Broadcast_channel_Description: 'Nur autorisierte Benutzer können neue Nachrichten schreiben, die anderen Benutzer können jedoch antworten',
|
||||||
Broadcast_Channel: 'Broadcastkanal',
|
Broadcast_Channel: 'Broadcastkanal',
|
||||||
|
@ -116,10 +124,13 @@ export default {
|
||||||
Channel_Name: 'Kanal Name',
|
Channel_Name: 'Kanal Name',
|
||||||
Channels: 'Kanäle',
|
Channels: 'Kanäle',
|
||||||
Chats: 'Chats',
|
Chats: 'Chats',
|
||||||
|
Call_already_ended: 'Anruf bereits beendet!',
|
||||||
|
Click_to_join: 'Klicken um teilzunehmen!',
|
||||||
Close: 'Schließen',
|
Close: 'Schließen',
|
||||||
Close_emoji_selector: 'Schließen Sie die Emoji-Auswahl',
|
Close_emoji_selector: 'Schließen Sie die Emoji-Auswahl',
|
||||||
Choose: 'Wählen',
|
Choose: 'Wählen',
|
||||||
Choose_from_library: 'Aus der Bibliothek auswählen',
|
Choose_from_library: 'Aus der Bibliothek auswählen',
|
||||||
|
Choose_file: 'Datei auswählen',
|
||||||
Code: 'Code',
|
Code: 'Code',
|
||||||
Collaborative: 'Kollaborativ',
|
Collaborative: 'Kollaborativ',
|
||||||
Confirm: 'Bestätigen',
|
Confirm: 'Bestätigen',
|
||||||
|
@ -129,32 +140,41 @@ export default {
|
||||||
connecting_server: 'verbinde zum Server',
|
connecting_server: 'verbinde zum Server',
|
||||||
Connecting: 'Verbinden ...',
|
Connecting: 'Verbinden ...',
|
||||||
Contact_us: 'Kontaktiere uns',
|
Contact_us: 'Kontaktiere uns',
|
||||||
|
Contact_your_server_admin: 'Kontaktieren Sie Ihren Server-Administrator.',
|
||||||
Continue_with: 'Weitermachen mit',
|
Continue_with: 'Weitermachen mit',
|
||||||
Copied_to_clipboard: 'In die Zwischenablage kopiert!',
|
Copied_to_clipboard: 'In die Zwischenablage kopiert!',
|
||||||
Copy: 'Kopieren',
|
Copy: 'Kopieren',
|
||||||
Permalink: 'Permalink',
|
Permalink: 'Permalink',
|
||||||
|
Certificate_password: 'Zertifikats-Passwort',
|
||||||
|
Whats_the_password_for_your_certificate: 'Wie lautet das Passwort für Ihr Zertifikat?',
|
||||||
Create_account: 'Ein Konto erstellen',
|
Create_account: 'Ein Konto erstellen',
|
||||||
Create_Channel: 'Kanal erstellen',
|
Create_Channel: 'Kanal erstellen',
|
||||||
Created_snippet: 'Erstellt ein Snippet',
|
Created_snippet: 'Erstellt ein Snippet',
|
||||||
Create_a_new_workspace: 'Erstellen Sie einen neuen Arbeitsbereich',
|
Create_a_new_workspace: 'Erstellen Sie einen neuen Arbeitsbereich',
|
||||||
Create: 'Erstellen',
|
Create: 'Erstellen',
|
||||||
|
Dark: 'Dunkel',
|
||||||
|
Dark_level: 'Dunkelstufe',
|
||||||
|
Default: 'Standard',
|
||||||
Delete_Room_Warning: 'Durch das Löschen eines Raums werden alle Nachrichten gelöscht, die im Raum gepostet wurden. Das kann nicht rückgängig gemacht werden.',
|
Delete_Room_Warning: 'Durch das Löschen eines Raums werden alle Nachrichten gelöscht, die im Raum gepostet wurden. Das kann nicht rückgängig gemacht werden.',
|
||||||
delete: 'löschen',
|
delete: 'löschen',
|
||||||
Delete: 'Löschen',
|
Delete: 'Löschen',
|
||||||
DELETE: 'LÖSCHEN',
|
DELETE: 'LÖSCHEN',
|
||||||
description: 'Beschreibung',
|
description: 'Beschreibung',
|
||||||
Description: 'Beschreibung',
|
Description: 'Beschreibung',
|
||||||
|
DESKTOP_OPTIONS: 'Desktop-Einstellungen',
|
||||||
Directory: 'Verzeichnis',
|
Directory: 'Verzeichnis',
|
||||||
Direct_Messages: 'Direkte Nachrichten',
|
Direct_Messages: 'Direkte Nachrichten',
|
||||||
Disable_notifications: 'Benachrichtigungen deaktiveren',
|
Disable_notifications: 'Benachrichtigungen deaktiveren',
|
||||||
Discussions: 'Diskussionen',
|
Discussions: 'Diskussionen',
|
||||||
Dont_Have_An_Account: 'Sie haben noch kein Konto?',
|
Dont_Have_An_Account: 'Sie haben noch kein Konto?',
|
||||||
|
Do_you_have_a_certificate: 'Haben Sie ein Zertifikat?',
|
||||||
Do_you_really_want_to_key_this_room_question_mark: 'Möchten Sie diesen Raum wirklich {{key}}?',
|
Do_you_really_want_to_key_this_room_question_mark: 'Möchten Sie diesen Raum wirklich {{key}}?',
|
||||||
edit: 'bearbeiten',
|
edit: 'bearbeiten',
|
||||||
edited: 'bearbeitet',
|
edited: 'bearbeitet',
|
||||||
Edit: 'Bearbeiten',
|
Edit: 'Bearbeiten',
|
||||||
Email_or_password_field_is_empty: 'Das E-Mail- oder Passwortfeld ist leer',
|
Email_or_password_field_is_empty: 'Das E-Mail- oder Passwortfeld ist leer',
|
||||||
Email: 'Email',
|
Email: 'Email',
|
||||||
|
EMAIL: 'EMAIL',
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
Enable_Auto_Translate: 'Automatische Übersetzung aktivieren',
|
Enable_Auto_Translate: 'Automatische Übersetzung aktivieren',
|
||||||
Enable_markdown: 'Markdown aktivieren',
|
Enable_markdown: 'Markdown aktivieren',
|
||||||
|
@ -174,12 +194,15 @@ export default {
|
||||||
Forgot_password_If_this_email_is_registered: 'Wenn diese E-Mail registriert ist, senden wir Anweisungen zum Zurücksetzen Ihres Passworts. Wenn Sie in Kürze keine E-Mail erhalten, kommen Sie bitte zurück und versuchen Sie es erneut.',
|
Forgot_password_If_this_email_is_registered: 'Wenn diese E-Mail registriert ist, senden wir Anweisungen zum Zurücksetzen Ihres Passworts. Wenn Sie in Kürze keine E-Mail erhalten, kommen Sie bitte zurück und versuchen Sie es erneut.',
|
||||||
Forgot_password: 'Passwort vergessen',
|
Forgot_password: 'Passwort vergessen',
|
||||||
Forgot_Password: 'Passwort vergessen',
|
Forgot_Password: 'Passwort vergessen',
|
||||||
|
Full_table: 'Klicken um die ganze Tabelle anzuzeigen',
|
||||||
Group_by_favorites: 'Nach Favoriten gruppieren',
|
Group_by_favorites: 'Nach Favoriten gruppieren',
|
||||||
Group_by_type: 'Gruppieren nach Typ',
|
Group_by_type: 'Gruppieren nach Typ',
|
||||||
Hide: 'Ausblenden',
|
Hide: 'Ausblenden',
|
||||||
Has_joined_the_channel: 'Ist dem Kanal beigetreten',
|
Has_joined_the_channel: 'Ist dem Kanal beigetreten',
|
||||||
Has_joined_the_conversation: 'Hat sich dem Gespräch angeschlossen',
|
Has_joined_the_conversation: 'Hat sich dem Gespräch angeschlossen',
|
||||||
Has_left_the_channel: 'Hat den Kanal verlassen',
|
Has_left_the_channel: 'Hat den Kanal verlassen',
|
||||||
|
IN_APP_AND_DESKTOP: 'IN-APP UND DESKTOP',
|
||||||
|
In_App_and_Desktop_Alert_info: 'Zeigt ein Banner oben am Bildschirm, wenn die App geöffnet ist und eine Benachrichtigung auf dem Desktop.',
|
||||||
Invisible: 'Unsichtbar',
|
Invisible: 'Unsichtbar',
|
||||||
Invite: 'Einladen',
|
Invite: 'Einladen',
|
||||||
is_a_valid_RocketChat_instance: 'ist eine gültige Rocket.Chat-Instanz',
|
is_a_valid_RocketChat_instance: 'ist eine gültige Rocket.Chat-Instanz',
|
||||||
|
@ -195,10 +218,11 @@ export default {
|
||||||
leaving_room: 'Raum verlassen',
|
leaving_room: 'Raum verlassen',
|
||||||
leave: 'verlassen',
|
leave: 'verlassen',
|
||||||
Legal: 'Rechtliches',
|
Legal: 'Rechtliches',
|
||||||
|
Light: 'Hell',
|
||||||
License: 'Lizenz',
|
License: 'Lizenz',
|
||||||
Livechat: 'Live-Chat',
|
Livechat: 'Live-Chat',
|
||||||
Login: 'Anmeldung',
|
Login: 'Anmeldung',
|
||||||
Login_error: 'Ihre Referenzen wurden abgelehnt! Bitte versuche es erneut.',
|
Login_error: 'Ihre Zugangsdaten wurden abgelehnt! Bitte versuchen Sie es erneut.',
|
||||||
Login_with: 'Einloggen mit',
|
Login_with: 'Einloggen mit',
|
||||||
Logout: 'Ausloggen',
|
Logout: 'Ausloggen',
|
||||||
members: 'Mitglieder',
|
members: 'Mitglieder',
|
||||||
|
@ -214,7 +238,7 @@ export default {
|
||||||
messages: 'Nachrichten',
|
messages: 'Nachrichten',
|
||||||
Messages: 'Mitteilungen',
|
Messages: 'Mitteilungen',
|
||||||
Message_Reported: 'Nachricht gemeldet',
|
Message_Reported: 'Nachricht gemeldet',
|
||||||
Microphone_Permission_Message: 'Rocket Chat benötigt Zugriff auf Ihr Mikrofon, damit Sie eine Audionachricht senden können.',
|
Microphone_Permission_Message: 'Rocket.Chat benötigt Zugriff auf Ihr Mikrofon, damit Sie eine Audionachricht senden können.',
|
||||||
Microphone_Permission: 'Mikrofonberechtigung',
|
Microphone_Permission: 'Mikrofonberechtigung',
|
||||||
Mute: 'Stumm',
|
Mute: 'Stumm',
|
||||||
muted: 'stummgeschaltet',
|
muted: 'stummgeschaltet',
|
||||||
|
@ -230,7 +254,7 @@ export default {
|
||||||
No_files: 'Keine Dateien',
|
No_files: 'Keine Dateien',
|
||||||
No_mentioned_messages: 'Keine erwähnten Nachrichten',
|
No_mentioned_messages: 'Keine erwähnten Nachrichten',
|
||||||
No_pinned_messages: 'Keine angehefteten Nachrichten',
|
No_pinned_messages: 'Keine angehefteten Nachrichten',
|
||||||
No_results_found: 'keine Ergebnisse gefunden',
|
No_results_found: 'Keine Ergebnisse gefunden',
|
||||||
No_starred_messages: 'Keine markierten Nachrichten',
|
No_starred_messages: 'Keine markierten Nachrichten',
|
||||||
No_thread_messages: 'Keine Threadnachrichten',
|
No_thread_messages: 'Keine Threadnachrichten',
|
||||||
No_announcement_provided: 'Keine Ankündigung erfolgt.',
|
No_announcement_provided: 'Keine Ankündigung erfolgt.',
|
||||||
|
@ -241,15 +265,20 @@ export default {
|
||||||
No_Reactions: 'Keine Reaktionen',
|
No_Reactions: 'Keine Reaktionen',
|
||||||
No_Read_Receipts: 'Keine Lesebestätigungen',
|
No_Read_Receipts: 'Keine Lesebestätigungen',
|
||||||
Not_logged: 'Nicht protokolliert',
|
Not_logged: 'Nicht protokolliert',
|
||||||
|
Not_RC_Server: 'Dies ist kein Rocket.Chat-Server.\n{{contact}}',
|
||||||
|
Nothing: 'Nichts',
|
||||||
Nothing_to_save: 'Nichts zu speichern!',
|
Nothing_to_save: 'Nichts zu speichern!',
|
||||||
Notify_active_in_this_room: 'Aktive Benutzer in diesem Raum benachrichtigen',
|
Notify_active_in_this_room: 'Aktive Benutzer in diesem Raum benachrichtigen',
|
||||||
Notify_all_in_this_room: 'Benachrichtigen Sie alle in diesem Raum',
|
Notify_all_in_this_room: 'Benachrichtigen Sie alle in diesem Raum',
|
||||||
|
Notifications: 'Benachrichtigungen',
|
||||||
|
Notification_Duration: 'Benachrichtigungsdauer',
|
||||||
|
Notification_Preferences: 'Benachrichtigungseinstellungen',
|
||||||
Offline: 'Offline',
|
Offline: 'Offline',
|
||||||
Oops: 'Hoppla!',
|
Oops: 'Hoppla!',
|
||||||
Online: 'Online',
|
Online: 'Online',
|
||||||
Only_authorized_users_can_write_new_messages: 'Nur autorisierte Benutzer können neue Nachrichten schreiben',
|
Only_authorized_users_can_write_new_messages: 'Nur autorisierte Benutzer können neue Nachrichten schreiben',
|
||||||
Open_emoji_selector: 'Öffne die Emoji-Auswahl',
|
Open_emoji_selector: 'Öffne die Emoji-Auswahl',
|
||||||
Open_Source_Communication: 'Open Source-Kommunikation',
|
Open_Source_Communication: 'Open-Source-Kommunikation',
|
||||||
Password: 'Passwort',
|
Password: 'Passwort',
|
||||||
Permalink_copied_to_clipboard: 'Permalink in die Zwischenablage kopiert!',
|
Permalink_copied_to_clipboard: 'Permalink in die Zwischenablage kopiert!',
|
||||||
Pin: 'Anheften',
|
Pin: 'Anheften',
|
||||||
|
@ -257,15 +286,19 @@ export default {
|
||||||
pinned: 'angeheftet',
|
pinned: 'angeheftet',
|
||||||
Pinned: 'Angeheftet',
|
Pinned: 'Angeheftet',
|
||||||
Please_enter_your_password: 'Bitte geben Sie Ihr Passwort ein',
|
Please_enter_your_password: 'Bitte geben Sie Ihr Passwort ein',
|
||||||
|
Preferences: 'Einstellungen',
|
||||||
Preferences_saved: 'Einstellungen gespeichert!',
|
Preferences_saved: 'Einstellungen gespeichert!',
|
||||||
Privacy_Policy: ' Datenschutzbestimmungen',
|
Privacy_Policy: ' Datenschutzbestimmungen',
|
||||||
Private_Channel: 'Privater Kanal',
|
Private_Channel: 'Privater Kanal',
|
||||||
Private_Groups: 'Private Gruppen',
|
Private_Groups: 'Private Gruppen',
|
||||||
Private: 'Privat',
|
Private: 'Privat',
|
||||||
|
Processing: 'Bearbeite …',
|
||||||
Profile_saved_successfully: 'Profil erfolgreich gespeichert!',
|
Profile_saved_successfully: 'Profil erfolgreich gespeichert!',
|
||||||
Profile: 'Profil',
|
Profile: 'Profil',
|
||||||
Public_Channel: 'Öffentlicher Kanal',
|
Public_Channel: 'Öffentlicher Kanal',
|
||||||
Public: 'Öffentlich',
|
Public: 'Öffentlich',
|
||||||
|
PUSH_NOTIFICATIONS: 'Push-Benachrichtigungen',
|
||||||
|
Push_Notifications_Alert_Info: 'Diese Benachrichtigungen werden Ihnen zugestellt, wenn die App nicht geöffnet ist.',
|
||||||
Quote: 'Zitat',
|
Quote: 'Zitat',
|
||||||
Reactions_are_disabled: 'Reaktionen sind deaktiviert',
|
Reactions_are_disabled: 'Reaktionen sind deaktiviert',
|
||||||
Reactions_are_enabled: 'Reaktionen sind aktiviert',
|
Reactions_are_enabled: 'Reaktionen sind aktiviert',
|
||||||
|
@ -274,6 +307,8 @@ export default {
|
||||||
Read_Only_Channel: 'Nur-Lese-Kanal',
|
Read_Only_Channel: 'Nur-Lese-Kanal',
|
||||||
Read_Only: 'Schreibgeschützt',
|
Read_Only: 'Schreibgeschützt',
|
||||||
Read_Receipt: 'Lesebestätigung',
|
Read_Receipt: 'Lesebestätigung',
|
||||||
|
Receive_Group_Mentions: 'Erhalte Gruppen-Benachrichtigungen',
|
||||||
|
Receive_Group_Mentions_Info: 'Empfange @all und @here Erwähnungen',
|
||||||
Register: 'Registrieren',
|
Register: 'Registrieren',
|
||||||
Repeat_Password: 'Wiederhole das Passwort',
|
Repeat_Password: 'Wiederhole das Passwort',
|
||||||
Replied_on: 'Antwortete am:',
|
Replied_on: 'Antwortete am:',
|
||||||
|
@ -281,6 +316,8 @@ export default {
|
||||||
reply: 'Antworten',
|
reply: 'Antworten',
|
||||||
Reply: 'Antworten',
|
Reply: 'Antworten',
|
||||||
Report: 'Bericht',
|
Report: 'Bericht',
|
||||||
|
Receive_Notification: 'Erhalte Benachrichtigungen',
|
||||||
|
Receive_notifications_from: 'Erhalte Benachrichtigungen von {{name}}',
|
||||||
Resend: 'Erneut senden',
|
Resend: 'Erneut senden',
|
||||||
Reset_password: 'Passwort zurücksetzen',
|
Reset_password: 'Passwort zurücksetzen',
|
||||||
resetting_password: 'Passwort zurücksetzen',
|
resetting_password: 'Passwort zurücksetzen',
|
||||||
|
@ -294,7 +331,7 @@ export default {
|
||||||
Room_Files: 'Raumdateien',
|
Room_Files: 'Raumdateien',
|
||||||
Room_Info_Edit: 'Rauminfo bearbeiten',
|
Room_Info_Edit: 'Rauminfo bearbeiten',
|
||||||
Room_Info: 'Rauminfo',
|
Room_Info: 'Rauminfo',
|
||||||
Room_Members: 'Raum Mitglieder',
|
Room_Members: 'Raum-Mitglieder',
|
||||||
Room_name_changed: 'Raumname geändert in {{name}} von {{userBy}}',
|
Room_name_changed: 'Raumname geändert in {{name}} von {{userBy}}',
|
||||||
SAVE: 'SPEICHERN',
|
SAVE: 'SPEICHERN',
|
||||||
Save_Changes: 'Änderungen speichern',
|
Save_Changes: 'Änderungen speichern',
|
||||||
|
@ -302,17 +339,21 @@ export default {
|
||||||
saving_preferences: 'Präferenzen speichern',
|
saving_preferences: 'Präferenzen speichern',
|
||||||
saving_profile: 'Profil speichern',
|
saving_profile: 'Profil speichern',
|
||||||
saving_settings: 'Einstellungen speichern',
|
saving_settings: 'Einstellungen speichern',
|
||||||
|
saved_to_gallery: 'Gespeichert in der Galerie',
|
||||||
Search_Messages: 'Nachrichten suchen',
|
Search_Messages: 'Nachrichten suchen',
|
||||||
Search: 'Suche',
|
Search: 'Suche',
|
||||||
Search_by: 'Suche nach',
|
Search_by: 'Suche nach',
|
||||||
Search_global_users: 'Suche nach globalen Benutzern',
|
Search_global_users: 'Suche nach globalen Benutzern',
|
||||||
Search_global_users_description: 'Beim Einschalten können Sie nach Benutzern von anderen Unternehmen oder Servern suchen.',
|
Search_global_users_description: 'Beim Einschalten können Sie nach Benutzern von anderen Unternehmen oder Servern suchen.',
|
||||||
|
Seconds: '{{second}} Sekunden',
|
||||||
Select_Avatar: 'Wählen Sie einen Avatar aus',
|
Select_Avatar: 'Wählen Sie einen Avatar aus',
|
||||||
|
Select_Server: 'Server auswählen',
|
||||||
Select_Users: 'Wählen Sie einen Benutzer aus',
|
Select_Users: 'Wählen Sie einen Benutzer aus',
|
||||||
Send: 'Senden',
|
Send: 'Senden',
|
||||||
Send_audio_message: 'Audio-Nachricht senden',
|
Send_audio_message: 'Audio-Nachricht senden',
|
||||||
Send_crash_report: 'Absturzbericht senden',
|
Send_crash_report: 'Absturzbericht senden',
|
||||||
Send_message: 'Nachricht senden',
|
Send_message: 'Nachricht senden',
|
||||||
|
Send_to: 'Senden an …',
|
||||||
Sent_an_attachment: 'Sende einen Anhang',
|
Sent_an_attachment: 'Sende einen Anhang',
|
||||||
Server: 'Server',
|
Server: 'Server',
|
||||||
Servers: 'Server',
|
Servers: 'Server',
|
||||||
|
@ -322,10 +363,13 @@ export default {
|
||||||
Settings_succesfully_changed: 'Einstellungen erfolgreich geändert!',
|
Settings_succesfully_changed: 'Einstellungen erfolgreich geändert!',
|
||||||
Share: 'Teilen',
|
Share: 'Teilen',
|
||||||
Share_this_app: 'Teile diese App',
|
Share_this_app: 'Teile diese App',
|
||||||
|
Show_Unread_Counter: 'Zähler anzeigen',
|
||||||
|
Show_Unread_Counter_Info: 'Anzahl der ungelesenen Nachrichten anzeigen',
|
||||||
Sign_in_your_server: 'Melden Sie sich bei Ihrem Server an',
|
Sign_in_your_server: 'Melden Sie sich bei Ihrem Server an',
|
||||||
Sign_Up: 'Anmelden',
|
Sign_Up: 'Anmelden',
|
||||||
Some_field_is_invalid_or_empty: 'Ein Feld ist ungültig oder leer',
|
Some_field_is_invalid_or_empty: 'Ein Feld ist ungültig oder leer',
|
||||||
Sorting_by: 'Sortierung nach {{key}}',
|
Sorting_by: 'Sortierung nach {{key}}',
|
||||||
|
Sound: 'Ton',
|
||||||
Star_room: 'Favorisierter Raum',
|
Star_room: 'Favorisierter Raum',
|
||||||
Star: 'Favoriten',
|
Star: 'Favoriten',
|
||||||
Starred_Messages: 'Favorisierte Nachrichten',
|
Starred_Messages: 'Favorisierte Nachrichten',
|
||||||
|
@ -333,19 +377,23 @@ export default {
|
||||||
Starred: 'Favorisiert',
|
Starred: 'Favorisiert',
|
||||||
Start_of_conversation: 'Beginn des Gesprächs',
|
Start_of_conversation: 'Beginn des Gesprächs',
|
||||||
Started_discussion: 'Hat eine Diskussion gestartet:',
|
Started_discussion: 'Hat eine Diskussion gestartet:',
|
||||||
|
Started_call: 'Anruf gestartet von {{userBy}}',
|
||||||
Submit: 'einreichen',
|
Submit: 'einreichen',
|
||||||
|
Table: 'Tabelle',
|
||||||
Take_a_photo: 'Foto aufnehmen',
|
Take_a_photo: 'Foto aufnehmen',
|
||||||
|
Take_a_video: 'Video aufnehmen',
|
||||||
tap_to_change_status: 'Tippen um den Status zu ändern',
|
tap_to_change_status: 'Tippen um den Status zu ändern',
|
||||||
Tap_to_view_servers_list: 'Tippen Sie hier, um die Serverliste anzuzeigen',
|
Tap_to_view_servers_list: 'Tippen Sie hier, um die Serverliste anzuzeigen',
|
||||||
Terms_of_Service: ' Nutzungsbedingungen',
|
Terms_of_Service: ' Nutzungsbedingungen',
|
||||||
Theme: 'Theme',
|
Theme: 'Theme',
|
||||||
The_URL_is_invalid: 'Die eingegebene URL ist ungültig. Überprüfen Sie es und versuchen Sie es erneut, bitte!',
|
The_URL_is_invalid: 'Die eingegebene URL ist ungültig. Überprüfen Sie es und versuchen Sie es bitte erneut!',
|
||||||
There_was_an_error_while_action: 'Während {{action}} ist ein Fehler aufgetreten!',
|
There_was_an_error_while_action: 'Während {{action}} ist ein Fehler aufgetreten!',
|
||||||
This_room_is_blocked: 'Dieser Raum ist gesperrt',
|
This_room_is_blocked: 'Dieser Raum ist gesperrt',
|
||||||
This_room_is_read_only: 'Dieser Raum kann nur gelesen werden',
|
This_room_is_read_only: 'Dieser Raum kann nur gelesen werden',
|
||||||
Thread: 'Thread',
|
Thread: 'Thread',
|
||||||
Threads: 'Threads',
|
Threads: 'Threads',
|
||||||
Timezone: 'Zeitzone',
|
Timezone: 'Zeitzone',
|
||||||
|
To: 'An',
|
||||||
topic: 'Thema',
|
topic: 'Thema',
|
||||||
Topic: 'Thema',
|
Topic: 'Thema',
|
||||||
Translate: 'Übersetzen',
|
Translate: 'Übersetzen',
|
||||||
|
@ -354,7 +402,7 @@ export default {
|
||||||
Type_the_channel_name_here: 'Geben Sie hier den Kanalnamen ein',
|
Type_the_channel_name_here: 'Geben Sie hier den Kanalnamen ein',
|
||||||
unarchive: 'wiederherstellen',
|
unarchive: 'wiederherstellen',
|
||||||
UNARCHIVE: 'WIEDERHERSTELLEN',
|
UNARCHIVE: 'WIEDERHERSTELLEN',
|
||||||
Unblock_user: 'Nutzer entblockieren',
|
Unblock_user: 'Nutzer entsperren',
|
||||||
Unfavorite: 'Nicht mehr favorisieren',
|
Unfavorite: 'Nicht mehr favorisieren',
|
||||||
Unfollowed_thread: 'Thread nicht mehr folgen',
|
Unfollowed_thread: 'Thread nicht mehr folgen',
|
||||||
Unmute: 'Stummschaltung aufheben',
|
Unmute: 'Stummschaltung aufheben',
|
||||||
|
@ -364,7 +412,7 @@ export default {
|
||||||
Unread: 'Ungelesen',
|
Unread: 'Ungelesen',
|
||||||
Unread_on_top: 'Ungelesen an der Spitze',
|
Unread_on_top: 'Ungelesen an der Spitze',
|
||||||
Unstar: 'von Favoriten entfernen',
|
Unstar: 'von Favoriten entfernen',
|
||||||
Updating: 'Aktualisierung...',
|
Updating: 'Aktualisierung …',
|
||||||
Uploading: 'Hochladen',
|
Uploading: 'Hochladen',
|
||||||
Upload_file_question_mark: 'Datei hochladen?',
|
Upload_file_question_mark: 'Datei hochladen?',
|
||||||
Users: 'Benutzer',
|
Users: 'Benutzer',
|
||||||
|
@ -383,9 +431,13 @@ export default {
|
||||||
Video_call: 'Videoanruf',
|
Video_call: 'Videoanruf',
|
||||||
View_Original: 'Original anzeigen',
|
View_Original: 'Original anzeigen',
|
||||||
Voice_call: 'Sprachanruf',
|
Voice_call: 'Sprachanruf',
|
||||||
|
Websocket_disabled: 'Websockets sind auf diesem Server nicht aktiviert.\n{{contact}}',
|
||||||
Welcome: 'Herzlich willkommen',
|
Welcome: 'Herzlich willkommen',
|
||||||
Welcome_to_RocketChat: 'Willkommen bei Rocket.Chat',
|
Welcome_to_RocketChat: 'Willkommen bei Rocket.Chat',
|
||||||
Whats_your_2fa: 'Wie ist dein 2FA-Code?',
|
Whats_your_2fa: 'Wie lautet Ihr 2FA-Code?',
|
||||||
|
Without_Servers: 'Ohne Server',
|
||||||
|
Write_External_Permission_Message: 'Rocket.Chat benötigt Zugriff auf Ihre Galerie um Bilder speichern zu können.',
|
||||||
|
Write_External_Permission: 'Galerie-Zugriff',
|
||||||
Yes_action_it: 'Ja, {{action}}!',
|
Yes_action_it: 'Ja, {{action}}!',
|
||||||
Yesterday: 'Gestern',
|
Yesterday: 'Gestern',
|
||||||
You_are_in_preview_mode: 'Sie befinden sich im Vorschaumodus',
|
You_are_in_preview_mode: 'Sie befinden sich im Vorschaumodus',
|
||||||
|
@ -393,10 +445,26 @@ export default {
|
||||||
You_can_search_using_RegExp_eg: 'Sie können mit RegExp suchen. z.B. `/ ^ text $ / i`',
|
You_can_search_using_RegExp_eg: 'Sie können mit RegExp suchen. z.B. `/ ^ text $ / i`',
|
||||||
You_colon: 'Sie: ',
|
You_colon: 'Sie: ',
|
||||||
you_were_mentioned: 'Sie wurden erwähnt',
|
you_were_mentioned: 'Sie wurden erwähnt',
|
||||||
you: 'sie',
|
you: 'Sie',
|
||||||
You: 'Sie',
|
You: 'Sie',
|
||||||
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Sie benötigen Zugang zu mindestens einem Rocket.Chat-Server um etwas zu teilen.',
|
||||||
|
Your_certificate: 'Ihr Zertifikat',
|
||||||
Version_no: 'Version: {{version}}',
|
Version_no: 'Version: {{version}}',
|
||||||
You_will_not_be_able_to_recover_this_message: 'Sie können diese Nachricht nicht wiederherstellen!',
|
You_will_not_be_able_to_recover_this_message: 'Sie können diese Nachricht nicht wiederherstellen!',
|
||||||
Change_Language: 'Sprache ändern',
|
Change_Language: 'Sprache ändern',
|
||||||
Crash_report_disclaimer: 'Wir verfolgen niemals den Inhalt Ihrer Chats. Der Crash-Report enthält nur für uns relevante Informationen in der Reihenfolge '
|
Crash_report_disclaimer: 'Wir verfolgen niemals den Inhalt Ihrer Chats. Der Crash-Report enthält nur für uns relevante Informationen um das Problem zu erkennen und zu beheben.',
|
||||||
|
Type_message: 'Type message',
|
||||||
|
Room_search: 'Raum-Suche',
|
||||||
|
Room_selection: 'Raum-Auswahl 1...9',
|
||||||
|
Next_room: 'Nächster Raum',
|
||||||
|
Previous_room: 'Voriger Raum',
|
||||||
|
New_room: 'Neuer Raum',
|
||||||
|
Upload_room: 'Zu einem Raum hochladen',
|
||||||
|
Search_messages: 'Nachrichten durchsuchen',
|
||||||
|
Scroll_messages: 'Nachrichten durchblättern',
|
||||||
|
Reply_latest: 'Auf die letzte Nachricht antworten',
|
||||||
|
Server_selection: 'Server-Auswahl',
|
||||||
|
Server_selection_numbers: 'Server-Auswahl 1...9',
|
||||||
|
Add_server: 'Server hinufügen',
|
||||||
|
New_line: 'Zeilenumbruch'
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,6 +16,7 @@ export default {
|
||||||
'error-duplicate-channel-name': 'A channel with name {{channel_name}} exists',
|
'error-duplicate-channel-name': 'A channel with name {{channel_name}} exists',
|
||||||
'error-email-domain-blacklisted': 'The email domain is blacklisted',
|
'error-email-domain-blacklisted': 'The email domain is blacklisted',
|
||||||
'error-email-send-failed': 'Error trying to send email: {{message}}',
|
'error-email-send-failed': 'Error trying to send email: {{message}}',
|
||||||
|
'error-save-image': 'Error while saving image',
|
||||||
'error-field-unavailable': '{{field}} is already in use :(',
|
'error-field-unavailable': '{{field}} is already in use :(',
|
||||||
'error-file-too-large': 'File is too large',
|
'error-file-too-large': 'File is too large',
|
||||||
'error-importer-not-defined': 'The importer was not defined correctly, it is missing the Import class.',
|
'error-importer-not-defined': 'The importer was not defined correctly, it is missing the Import class.',
|
||||||
|
@ -80,7 +81,7 @@ export default {
|
||||||
Activity: 'Activity',
|
Activity: 'Activity',
|
||||||
Add_Reaction: 'Add Reaction',
|
Add_Reaction: 'Add Reaction',
|
||||||
Add_Server: 'Add Server',
|
Add_Server: 'Add Server',
|
||||||
Add_user: 'Add user',
|
Add_users: 'Add users',
|
||||||
Admin_Panel: 'Admin Panel',
|
Admin_Panel: 'Admin Panel',
|
||||||
Alert: 'Alert',
|
Alert: 'Alert',
|
||||||
alert: 'alert',
|
alert: 'alert',
|
||||||
|
@ -120,6 +121,7 @@ export default {
|
||||||
Cancel: 'Cancel',
|
Cancel: 'Cancel',
|
||||||
changing_avatar: 'changing avatar',
|
changing_avatar: 'changing avatar',
|
||||||
creating_channel: 'creating channel',
|
creating_channel: 'creating channel',
|
||||||
|
creating_invite: 'creating invite',
|
||||||
Channel_Name: 'Channel Name',
|
Channel_Name: 'Channel Name',
|
||||||
Channels: 'Channels',
|
Channels: 'Channels',
|
||||||
Chats: 'Chats',
|
Chats: 'Chats',
|
||||||
|
@ -171,6 +173,7 @@ export default {
|
||||||
edit: 'edit',
|
edit: 'edit',
|
||||||
edited: 'edited',
|
edited: 'edited',
|
||||||
Edit: 'Edit',
|
Edit: 'Edit',
|
||||||
|
Edit_Invite: 'Edit Invite',
|
||||||
Email_or_password_field_is_empty: 'Email or password field is empty',
|
Email_or_password_field_is_empty: 'Email or password field is empty',
|
||||||
Email: 'Email',
|
Email: 'Email',
|
||||||
EMAIL: 'EMAIL',
|
EMAIL: 'EMAIL',
|
||||||
|
@ -181,6 +184,7 @@ export default {
|
||||||
Everyone_can_access_this_channel: 'Everyone can access this channel',
|
Everyone_can_access_this_channel: 'Everyone can access this channel',
|
||||||
erasing_room: 'erasing room',
|
erasing_room: 'erasing room',
|
||||||
Error_uploading: 'Error uploading',
|
Error_uploading: 'Error uploading',
|
||||||
|
Expiration_Days: 'Expiration (Days)',
|
||||||
Favorite: 'Favorite',
|
Favorite: 'Favorite',
|
||||||
Favorites: 'Favorites',
|
Favorites: 'Favorites',
|
||||||
Files: 'Files',
|
Files: 'Files',
|
||||||
|
@ -194,6 +198,7 @@ export default {
|
||||||
Forgot_password: 'Forgot password',
|
Forgot_password: 'Forgot password',
|
||||||
Forgot_Password: 'Forgot Password',
|
Forgot_Password: 'Forgot Password',
|
||||||
Full_table: 'Click to see full table',
|
Full_table: 'Click to see full table',
|
||||||
|
Generate_New_Link: 'Generate New Link',
|
||||||
Group_by_favorites: 'Group favorites',
|
Group_by_favorites: 'Group favorites',
|
||||||
Group_by_type: 'Group by type',
|
Group_by_type: 'Group by type',
|
||||||
Hide: 'Hide',
|
Hide: 'Hide',
|
||||||
|
@ -207,7 +212,10 @@ export default {
|
||||||
is_a_valid_RocketChat_instance: 'is a valid Rocket.Chat instance',
|
is_a_valid_RocketChat_instance: 'is a valid Rocket.Chat instance',
|
||||||
is_not_a_valid_RocketChat_instance: 'is not a valid Rocket.Chat instance',
|
is_not_a_valid_RocketChat_instance: 'is not a valid Rocket.Chat instance',
|
||||||
is_typing: 'is typing',
|
is_typing: 'is typing',
|
||||||
|
Invalid_or_expired_invite_token: 'Invalid or expired invite token',
|
||||||
Invalid_server_version: 'The server you\'re trying to connect is using a version that\'s not supported by the app anymore: {{currentVersion}}.\n\nWe require version {{minVersion}}',
|
Invalid_server_version: 'The server you\'re trying to connect is using a version that\'s not supported by the app anymore: {{currentVersion}}.\n\nWe require version {{minVersion}}',
|
||||||
|
Invite_Link: 'Invite Link',
|
||||||
|
Invite_users: 'Invite users',
|
||||||
Join_the_community: 'Join the community',
|
Join_the_community: 'Join the community',
|
||||||
Join: 'Join',
|
Join: 'Join',
|
||||||
Just_invited_people_can_access_this_channel: 'Just invited people can access this channel',
|
Just_invited_people_can_access_this_channel: 'Just invited people can access this channel',
|
||||||
|
@ -224,6 +232,7 @@ export default {
|
||||||
Login_error: 'Your credentials were rejected! Please try again.',
|
Login_error: 'Your credentials were rejected! Please try again.',
|
||||||
Login_with: 'Login with',
|
Login_with: 'Login with',
|
||||||
Logout: 'Logout',
|
Logout: 'Logout',
|
||||||
|
Max_number_of_uses: 'Max number of uses',
|
||||||
members: 'members',
|
members: 'members',
|
||||||
Members: 'Members',
|
Members: 'Members',
|
||||||
Mentioned_Messages: 'Mentioned Messages',
|
Mentioned_Messages: 'Mentioned Messages',
|
||||||
|
@ -246,11 +255,13 @@ export default {
|
||||||
N_users: '{{n}} users',
|
N_users: '{{n}} users',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
Name: 'Name',
|
Name: 'Name',
|
||||||
|
Never: 'Never',
|
||||||
New_Message: 'New Message',
|
New_Message: 'New Message',
|
||||||
New_Password: 'New Password',
|
New_Password: 'New Password',
|
||||||
New_Server: 'New Server',
|
New_Server: 'New Server',
|
||||||
Next: 'Next',
|
Next: 'Next',
|
||||||
No_files: 'No files',
|
No_files: 'No files',
|
||||||
|
No_limit: 'No limit',
|
||||||
No_mentioned_messages: 'No mentioned messages',
|
No_mentioned_messages: 'No mentioned messages',
|
||||||
No_pinned_messages: 'No pinned messages',
|
No_pinned_messages: 'No pinned messages',
|
||||||
No_results_found: 'No results found',
|
No_results_found: 'No results found',
|
||||||
|
@ -338,6 +349,7 @@ export default {
|
||||||
saving_preferences: 'saving preferences',
|
saving_preferences: 'saving preferences',
|
||||||
saving_profile: 'saving profile',
|
saving_profile: 'saving profile',
|
||||||
saving_settings: 'saving settings',
|
saving_settings: 'saving settings',
|
||||||
|
saved_to_gallery: 'Saved to gallery',
|
||||||
Search_Messages: 'Search Messages',
|
Search_Messages: 'Search Messages',
|
||||||
Search: 'Search',
|
Search: 'Search',
|
||||||
Search_by: 'Search by',
|
Search_by: 'Search by',
|
||||||
|
@ -360,6 +372,7 @@ export default {
|
||||||
Settings: 'Settings',
|
Settings: 'Settings',
|
||||||
Settings_succesfully_changed: 'Settings succesfully changed!',
|
Settings_succesfully_changed: 'Settings succesfully changed!',
|
||||||
Share: 'Share',
|
Share: 'Share',
|
||||||
|
Share_Link: 'Share Link',
|
||||||
Share_this_app: 'Share this app',
|
Share_this_app: 'Share this app',
|
||||||
Show_Unread_Counter: 'Show Unread Counter',
|
Show_Unread_Counter: 'Show Unread Counter',
|
||||||
Show_Unread_Counter_Info: 'Unread counter is displayed as a badge on the right of the channel, in the list',
|
Show_Unread_Counter_Info: 'Unread counter is displayed as a badge on the right of the channel, in the list',
|
||||||
|
@ -415,6 +428,7 @@ export default {
|
||||||
Upload_file_question_mark: 'Upload file?',
|
Upload_file_question_mark: 'Upload file?',
|
||||||
Users: 'Users',
|
Users: 'Users',
|
||||||
User_added_by: 'User {{userAdded}} added by {{userBy}}',
|
User_added_by: 'User {{userAdded}} added by {{userBy}}',
|
||||||
|
User_Info: 'User Info',
|
||||||
User_has_been_key: 'User has been {{key}}!',
|
User_has_been_key: 'User has been {{key}}!',
|
||||||
User_is_no_longer_role_by_: '{{user}} is no longer {{role}} by {{userBy}}',
|
User_is_no_longer_role_by_: '{{user}} is no longer {{role}} by {{userBy}}',
|
||||||
User_muted_by: 'User {{userMuted}} muted by {{userBy}}',
|
User_muted_by: 'User {{userMuted}} muted by {{userBy}}',
|
||||||
|
@ -434,6 +448,8 @@ export default {
|
||||||
Welcome_to_RocketChat: 'Welcome to Rocket.Chat',
|
Welcome_to_RocketChat: 'Welcome to Rocket.Chat',
|
||||||
Whats_your_2fa: 'What\'s your 2FA code?',
|
Whats_your_2fa: 'What\'s your 2FA code?',
|
||||||
Without_Servers: 'Without Servers',
|
Without_Servers: 'Without Servers',
|
||||||
|
Write_External_Permission_Message: 'Rocket Chat needs access to your gallery so you can save images.',
|
||||||
|
Write_External_Permission: 'Gallery Permission',
|
||||||
Yes_action_it: 'Yes, {{action}} it!',
|
Yes_action_it: 'Yes, {{action}} it!',
|
||||||
Yesterday: 'Yesterday',
|
Yesterday: 'Yesterday',
|
||||||
You_are_in_preview_mode: 'You are in preview mode',
|
You_are_in_preview_mode: 'You are in preview mode',
|
||||||
|
@ -445,6 +461,10 @@ export default {
|
||||||
You: 'You',
|
You: 'You',
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
|
||||||
Your_certificate: 'Your Certificate',
|
Your_certificate: 'Your Certificate',
|
||||||
|
Your_invite_link_will_expire_after__usesLeft__uses: 'Your invite link will expire after {{usesLeft}} uses.',
|
||||||
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Your invite link will expire on {{date}} or after {{usesLeft}} uses.',
|
||||||
|
Your_invite_link_will_expire_on__date__: 'Your invite link will expire on {{date}}.',
|
||||||
|
Your_invite_link_will_never_expire: 'Your invite link will never expire.',
|
||||||
Version_no: 'Version: {{version}}',
|
Version_no: 'Version: {{version}}',
|
||||||
You_will_not_be_able_to_recover_this_message: 'You will not be able to recover this message!',
|
You_will_not_be_able_to_recover_this_message: 'You will not be able to recover this message!',
|
||||||
Change_Language: 'Change Language',
|
Change_Language: 'Change Language',
|
||||||
|
|
|
@ -80,7 +80,7 @@ export default {
|
||||||
Activity: 'Activité',
|
Activity: 'Activité',
|
||||||
Add_Reaction: 'Ajouter une réaction',
|
Add_Reaction: 'Ajouter une réaction',
|
||||||
Add_Server: 'Ajouter un serveur',
|
Add_Server: 'Ajouter un serveur',
|
||||||
Add_user: 'Ajouter un utilisateur',
|
Add_users: 'Ajouter des utilisateurs',
|
||||||
Alert: 'Alerte',
|
Alert: 'Alerte',
|
||||||
alert: 'alerte',
|
alert: 'alerte',
|
||||||
alerts: 'alertes',
|
alerts: 'alertes',
|
||||||
|
|
|
@ -16,6 +16,7 @@ export default {
|
||||||
'error-duplicate-channel-name': 'Já existe um canal com nome {{channel_name}}',
|
'error-duplicate-channel-name': 'Já existe um canal com nome {{channel_name}}',
|
||||||
'error-email-domain-blacklisted': 'O domínio de e-mail está na lista negra',
|
'error-email-domain-blacklisted': 'O domínio de e-mail está na lista negra',
|
||||||
'error-email-send-failed': 'Erro ao tentar enviar e-mail: {{message}}',
|
'error-email-send-failed': 'Erro ao tentar enviar e-mail: {{message}}',
|
||||||
|
'error-save-image': 'Erro ao salvar imagem',
|
||||||
'error-field-unavailable': '{{field}} já está sendo usado :(',
|
'error-field-unavailable': '{{field}} já está sendo usado :(',
|
||||||
'error-file-too-large': 'Arquivo é muito grande',
|
'error-file-too-large': 'Arquivo é muito grande',
|
||||||
'error-importer-not-defined': 'O importador não foi definido corretamente; está faltando a classe Import.',
|
'error-importer-not-defined': 'O importador não foi definido corretamente; está faltando a classe Import.',
|
||||||
|
@ -87,7 +88,7 @@ export default {
|
||||||
Activity: 'Atividade',
|
Activity: 'Atividade',
|
||||||
Add_Reaction: 'Reagir',
|
Add_Reaction: 'Reagir',
|
||||||
Add_Server: 'Adicionar servidor',
|
Add_Server: 'Adicionar servidor',
|
||||||
Add_user: 'Adicionar usuário',
|
Add_users: 'Adicionar usuário',
|
||||||
Alert: 'Alerta',
|
Alert: 'Alerta',
|
||||||
alert: 'alerta',
|
alert: 'alerta',
|
||||||
alerts: 'alertas',
|
alerts: 'alertas',
|
||||||
|
@ -122,6 +123,7 @@ export default {
|
||||||
Cancel: 'Cancelar',
|
Cancel: 'Cancelar',
|
||||||
changing_avatar: 'trocando avatar',
|
changing_avatar: 'trocando avatar',
|
||||||
creating_channel: 'criando canal',
|
creating_channel: 'criando canal',
|
||||||
|
creating_invite: 'criando convite',
|
||||||
Channel_Name: 'Nome do Canal',
|
Channel_Name: 'Nome do Canal',
|
||||||
Channels: 'Canais',
|
Channels: 'Canais',
|
||||||
Chats: 'Conversas',
|
Chats: 'Conversas',
|
||||||
|
@ -168,6 +170,7 @@ export default {
|
||||||
edited: 'editado',
|
edited: 'editado',
|
||||||
erasing_room: 'apagando sala',
|
erasing_room: 'apagando sala',
|
||||||
Edit: 'Editar',
|
Edit: 'Editar',
|
||||||
|
Edit_Invite: 'Editar convite',
|
||||||
Email_or_password_field_is_empty: 'Email ou senha estão vazios',
|
Email_or_password_field_is_empty: 'Email ou senha estão vazios',
|
||||||
Email: 'Email',
|
Email: 'Email',
|
||||||
email: 'e-mail',
|
email: 'e-mail',
|
||||||
|
@ -175,6 +178,7 @@ export default {
|
||||||
Enable_notifications: 'Habilitar notificações',
|
Enable_notifications: 'Habilitar notificações',
|
||||||
Everyone_can_access_this_channel: 'Todos podem acessar este canal',
|
Everyone_can_access_this_channel: 'Todos podem acessar este canal',
|
||||||
Error_uploading: 'Erro subindo',
|
Error_uploading: 'Erro subindo',
|
||||||
|
Expiration_Days: 'Expira em (dias)',
|
||||||
Favorites: 'Favoritos',
|
Favorites: 'Favoritos',
|
||||||
Files: 'Arquivos',
|
Files: 'Arquivos',
|
||||||
File_description: 'Descrição do arquivo',
|
File_description: 'Descrição do arquivo',
|
||||||
|
@ -187,6 +191,7 @@ export default {
|
||||||
Forgot_password: 'Esqueci minha senha',
|
Forgot_password: 'Esqueci minha senha',
|
||||||
Forgot_Password: 'Esqueci minha senha',
|
Forgot_Password: 'Esqueci minha senha',
|
||||||
Full_table: 'Clique para ver a tabela completa',
|
Full_table: 'Clique para ver a tabela completa',
|
||||||
|
Generate_New_Link: 'Gerar novo convite',
|
||||||
Group_by_favorites: 'Agrupar favoritos',
|
Group_by_favorites: 'Agrupar favoritos',
|
||||||
Group_by_type: 'Agrupar por tipo',
|
Group_by_type: 'Agrupar por tipo',
|
||||||
Has_joined_the_channel: 'Entrou no canal',
|
Has_joined_the_channel: 'Entrou no canal',
|
||||||
|
@ -195,7 +200,10 @@ export default {
|
||||||
Invisible: 'Invisível',
|
Invisible: 'Invisível',
|
||||||
Invite: 'Convidar',
|
Invite: 'Convidar',
|
||||||
is_typing: 'está digitando',
|
is_typing: 'está digitando',
|
||||||
|
Invalid_or_expired_invite_token: 'Token de convite inválido ou vencido',
|
||||||
Invalid_server_version: 'O servidor que você está conectando não é suportado mais por esta versão do aplicativo: {{currentVersion}}.\n\nEsta versão do aplicativo requer a versão {{minVersion}} do servidor para funcionar corretamente.',
|
Invalid_server_version: 'O servidor que você está conectando não é suportado mais por esta versão do aplicativo: {{currentVersion}}.\n\nEsta versão do aplicativo requer a versão {{minVersion}} do servidor para funcionar corretamente.',
|
||||||
|
Invite_Link: 'Link de Convite',
|
||||||
|
Invite_users: 'Convidar usuários',
|
||||||
Join_the_community: 'Junte-se à comunidade',
|
Join_the_community: 'Junte-se à comunidade',
|
||||||
Join: 'Entrar',
|
Join: 'Entrar',
|
||||||
Just_invited_people_can_access_this_channel: 'Apenas as pessoas convidadas podem acessar este canal',
|
Just_invited_people_can_access_this_channel: 'Apenas as pessoas convidadas podem acessar este canal',
|
||||||
|
@ -211,6 +219,7 @@ export default {
|
||||||
Login_error: 'Suas credenciais foram rejeitadas. Tente novamente por favor!',
|
Login_error: 'Suas credenciais foram rejeitadas. Tente novamente por favor!',
|
||||||
Login_with: 'Login with',
|
Login_with: 'Login with',
|
||||||
Logout: 'Sair',
|
Logout: 'Sair',
|
||||||
|
Max_number_of_uses: 'Número máximo de usos',
|
||||||
Members: 'Membros',
|
Members: 'Membros',
|
||||||
Mentioned_Messages: 'Mensagens mencionadas',
|
Mentioned_Messages: 'Mensagens mencionadas',
|
||||||
mentioned: 'mencionado',
|
mentioned: 'mencionado',
|
||||||
|
@ -230,11 +239,13 @@ export default {
|
||||||
N_users: '{{n}} usuários',
|
N_users: '{{n}} usuários',
|
||||||
name: 'nome',
|
name: 'nome',
|
||||||
Name: 'Nome',
|
Name: 'Nome',
|
||||||
|
Never: 'Nunca',
|
||||||
New_in_RocketChat_question_mark: 'Novo no Rocket.Chat?',
|
New_in_RocketChat_question_mark: 'Novo no Rocket.Chat?',
|
||||||
New_Message: 'Nova Mensagem',
|
New_Message: 'Nova Mensagem',
|
||||||
New_Password: 'Nova Senha',
|
New_Password: 'Nova Senha',
|
||||||
Next: 'Próximo',
|
Next: 'Próximo',
|
||||||
No_files: 'Não há arquivos',
|
No_files: 'Não há arquivos',
|
||||||
|
No_limit: 'Sem limite',
|
||||||
No_mentioned_messages: 'Não há menções',
|
No_mentioned_messages: 'Não há menções',
|
||||||
No_pinned_messages: 'Não há mensagens fixadas',
|
No_pinned_messages: 'Não há mensagens fixadas',
|
||||||
No_results_found: 'Nenhum resultado encontrado',
|
No_results_found: 'Nenhum resultado encontrado',
|
||||||
|
@ -308,6 +319,7 @@ export default {
|
||||||
saving_preferences: 'salvando preferências',
|
saving_preferences: 'salvando preferências',
|
||||||
saving_profile: 'salvando perfil',
|
saving_profile: 'salvando perfil',
|
||||||
saving_settings: 'salvando configurações',
|
saving_settings: 'salvando configurações',
|
||||||
|
saved_to_gallery: 'Salvo na galeria',
|
||||||
Search_Messages: 'Buscar Mensagens',
|
Search_Messages: 'Buscar Mensagens',
|
||||||
Search: 'Buscar',
|
Search: 'Buscar',
|
||||||
Search_by: 'Buscar por',
|
Search_by: 'Buscar por',
|
||||||
|
@ -326,6 +338,7 @@ export default {
|
||||||
Settings: 'Configurações',
|
Settings: 'Configurações',
|
||||||
Settings_succesfully_changed: 'Configurações salvas com sucesso!',
|
Settings_succesfully_changed: 'Configurações salvas com sucesso!',
|
||||||
Share: 'Compartilhar',
|
Share: 'Compartilhar',
|
||||||
|
Share_Link: 'Share Link',
|
||||||
Sign_in_your_server: 'Entrar no seu servidor',
|
Sign_in_your_server: 'Entrar no seu servidor',
|
||||||
Sign_Up: 'Registrar',
|
Sign_Up: 'Registrar',
|
||||||
Some_field_is_invalid_or_empty: 'Algum campo está inválido ou vazio',
|
Some_field_is_invalid_or_empty: 'Algum campo está inválido ou vazio',
|
||||||
|
@ -399,8 +412,13 @@ export default {
|
||||||
you_were_mentioned: 'você foi mencionado',
|
you_were_mentioned: 'você foi mencionado',
|
||||||
you: 'você',
|
you: 'você',
|
||||||
You: 'Você',
|
You: 'Você',
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Você precisa acessar ao menos um servidor Rocket.Chat para compartilhar.',
|
Your_invite_link_will_expire_after__usesLeft__uses: 'Seu link de convite irá vencer depois de {{usesLeft}} usos.',
|
||||||
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Seu link de convite irá vencer em {{date}} ou depois de {{usesLeft}} usos.',
|
||||||
|
Your_invite_link_will_expire_on__date__: 'Seu link de convite irá vencer em {{date}}.',
|
||||||
|
Your_invite_link_will_never_expire: 'Seu link de convite nunca irá vencer.',
|
||||||
You_will_not_be_able_to_recover_this_message: 'Você não será capaz de recuperar essa mensagem!',
|
You_will_not_be_able_to_recover_this_message: 'Você não será capaz de recuperar essa mensagem!',
|
||||||
|
Write_External_Permission_Message: 'Rocket Chat precisa de acesso à sua galeria para salvar imagens',
|
||||||
|
Write_External_Permission: 'Acesso à Galeria',
|
||||||
Crash_report_disclaimer: 'Nós não rastreamos o conteúdo das suas conversas. O relatório de erros apenas contém informações relevantes para identificarmos problemas e corrigí-los.',
|
Crash_report_disclaimer: 'Nós não rastreamos o conteúdo das suas conversas. O relatório de erros apenas contém informações relevantes para identificarmos problemas e corrigí-los.',
|
||||||
Type_message: 'Digitar mensagem',
|
Type_message: 'Digitar mensagem',
|
||||||
Room_search: 'Busca de sala',
|
Room_search: 'Busca de sala',
|
||||||
|
|
|
@ -80,7 +80,7 @@ export default {
|
||||||
Activity: 'Actividade',
|
Activity: 'Actividade',
|
||||||
Add_Reaction: 'Adicionar Reacção',
|
Add_Reaction: 'Adicionar Reacção',
|
||||||
Add_Server: 'Adicionar Servidor',
|
Add_Server: 'Adicionar Servidor',
|
||||||
Add_user: 'Adicionar utilizador',
|
Add_users: 'Adicionar utilizadores',
|
||||||
Alert: 'Alerta',
|
Alert: 'Alerta',
|
||||||
alert: 'alerta',
|
alert: 'alerta',
|
||||||
alerts: 'alertas',
|
alerts: 'alertas',
|
||||||
|
|
|
@ -80,7 +80,7 @@ export default {
|
||||||
Activity: 'Активность',
|
Activity: 'Активность',
|
||||||
Add_Reaction: 'Добавить реакцию',
|
Add_Reaction: 'Добавить реакцию',
|
||||||
Add_Server: 'Добавить сервер',
|
Add_Server: 'Добавить сервер',
|
||||||
Add_user: 'Добавить пользователя',
|
Add_users: 'Добавить пользователей',
|
||||||
Admin_Panel: 'Панель админа',
|
Admin_Panel: 'Панель админа',
|
||||||
Alert: 'Оповещение',
|
Alert: 'Оповещение',
|
||||||
alert: 'оповещение',
|
alert: 'оповещение',
|
||||||
|
|
|
@ -80,7 +80,7 @@ export default {
|
||||||
Activity: '按活动排序',
|
Activity: '按活动排序',
|
||||||
Add_Reaction: '增加回复',
|
Add_Reaction: '增加回复',
|
||||||
Add_Server: '添加服务器',
|
Add_Server: '添加服务器',
|
||||||
Add_user: '添加用户',
|
Add_users: '添加用户',
|
||||||
Alert: '警告',
|
Alert: '警告',
|
||||||
alert: '警告',
|
alert: '警告',
|
||||||
alerts: '警告',
|
alerts: '警告',
|
||||||
|
|
31
app/index.js
|
@ -25,7 +25,9 @@ import parseQuery from './lib/methods/helpers/parseQuery';
|
||||||
import { initializePushNotifications, onNotification } from './notifications/push';
|
import { initializePushNotifications, onNotification } from './notifications/push';
|
||||||
import store from './lib/createStore';
|
import store from './lib/createStore';
|
||||||
import NotificationBadge from './notifications/inApp';
|
import NotificationBadge from './notifications/inApp';
|
||||||
import { defaultHeader, onNavigationStateChange, cardStyle } from './utils/navigation';
|
import {
|
||||||
|
defaultHeader, onNavigationStateChange, cardStyle, getActiveRouteName
|
||||||
|
} from './utils/navigation';
|
||||||
import { loggerConfig, analytics } from './utils/log';
|
import { loggerConfig, analytics } from './utils/log';
|
||||||
import Toast from './containers/Toast';
|
import Toast from './containers/Toast';
|
||||||
import { ThemeContext } from './theme';
|
import { ThemeContext } from './theme';
|
||||||
|
@ -47,7 +49,7 @@ if (isIOS) {
|
||||||
const parseDeepLinking = (url) => {
|
const parseDeepLinking = (url) => {
|
||||||
if (url) {
|
if (url) {
|
||||||
url = url.replace(/rocketchat:\/\/|https:\/\/go.rocket.chat\//, '');
|
url = url.replace(/rocketchat:\/\/|https:\/\/go.rocket.chat\//, '');
|
||||||
const regex = /^(room|auth)\?/;
|
const regex = /^(room|auth|invite)\?/;
|
||||||
if (url.match(regex)) {
|
if (url.match(regex)) {
|
||||||
url = url.replace(regex, '').trim();
|
url = url.replace(regex, '').trim();
|
||||||
if (url) {
|
if (url) {
|
||||||
|
@ -144,6 +146,12 @@ const ChatsStack = createStackNavigator({
|
||||||
SelectedUsersView: {
|
SelectedUsersView: {
|
||||||
getScreen: () => require('./views/SelectedUsersView').default
|
getScreen: () => require('./views/SelectedUsersView').default
|
||||||
},
|
},
|
||||||
|
InviteUsersView: {
|
||||||
|
getScreen: () => require('./views/InviteUsersView').default
|
||||||
|
},
|
||||||
|
InviteUsersEditView: {
|
||||||
|
getScreen: () => require('./views/InviteUsersEditView').default
|
||||||
|
},
|
||||||
MessagesView: {
|
MessagesView: {
|
||||||
getScreen: () => require('./views/MessagesView').default
|
getScreen: () => require('./views/MessagesView').default
|
||||||
},
|
},
|
||||||
|
@ -258,9 +266,19 @@ const NewMessageStack = createStackNavigator({
|
||||||
cardStyle
|
cardStyle
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const AttachmentStack = createStackNavigator({
|
||||||
|
AttachmentView: {
|
||||||
|
getScreen: () => require('./views/AttachmentView').default
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
defaultNavigationOptions: defaultHeader,
|
||||||
|
cardStyle
|
||||||
|
});
|
||||||
|
|
||||||
const InsideStackModal = createStackNavigator({
|
const InsideStackModal = createStackNavigator({
|
||||||
Main: ChatsDrawer,
|
Main: ChatsDrawer,
|
||||||
NewMessageStack,
|
NewMessageStack,
|
||||||
|
AttachmentStack,
|
||||||
JitsiMeetView: {
|
JitsiMeetView: {
|
||||||
getScreen: () => require('./views/JitsiMeetView').default
|
getScreen: () => require('./views/JitsiMeetView').default
|
||||||
}
|
}
|
||||||
|
@ -388,6 +406,9 @@ const RoomActionsStack = createStackNavigator({
|
||||||
},
|
},
|
||||||
NotificationPrefView: {
|
NotificationPrefView: {
|
||||||
getScreen: () => require('./views/NotificationPreferencesView').default
|
getScreen: () => require('./views/NotificationPreferencesView').default
|
||||||
|
},
|
||||||
|
AttachmentView: {
|
||||||
|
getScreen: () => require('./views/AttachmentView').default
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
defaultNavigationOptions: defaultHeader,
|
defaultNavigationOptions: defaultHeader,
|
||||||
|
@ -439,6 +460,10 @@ class CustomModalStack extends React.Component {
|
||||||
const {
|
const {
|
||||||
navigation, showModal, closeModal, screenProps
|
navigation, showModal, closeModal, screenProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const pageSheetViews = ['AttachmentView'];
|
||||||
|
const pageSheet = pageSheetViews.includes(getActiveRouteName(navigation.state));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
useNativeDriver
|
useNativeDriver
|
||||||
|
@ -448,7 +473,7 @@ class CustomModalStack extends React.Component {
|
||||||
hideModalContentWhileAnimating
|
hideModalContentWhileAnimating
|
||||||
avoidKeyboard
|
avoidKeyboard
|
||||||
>
|
>
|
||||||
<View style={sharedStyles.modal}>
|
<View style={[sharedStyles.modal, pageSheet ? sharedStyles.modalPageSheet : sharedStyles.modalFormSheet]}>
|
||||||
<ModalSwitch navigation={navigation} screenProps={screenProps} />
|
<ModalSwitch navigation={navigation} screenProps={screenProps} />
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -18,12 +18,9 @@ function normalizeAttachments(msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (msg) => {
|
export default (msg) => {
|
||||||
/**
|
if (!msg) {
|
||||||
* 2019-03-29: Realm object properties are *always* optional, but `u.username` is required
|
return null;
|
||||||
* https://realm.io/docs/javascript/latest/#to-one-relationships
|
}
|
||||||
*/
|
|
||||||
if (!msg || !msg.u || !msg.u.username) { return; }
|
|
||||||
|
|
||||||
msg = normalizeAttachments(msg);
|
msg = normalizeAttachments(msg);
|
||||||
msg.reactions = msg.reactions || [];
|
msg.reactions = msg.reactions || [];
|
||||||
msg.unread = msg.unread || false;
|
msg.unread = msg.unread || false;
|
||||||
|
|
|
@ -5,6 +5,36 @@ import database from '../database';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import random from '../../utils/random';
|
import random from '../../utils/random';
|
||||||
|
|
||||||
|
const changeMessageStatus = async(id, tmid, status) => {
|
||||||
|
const db = database.active;
|
||||||
|
const msgCollection = db.collections.get('messages');
|
||||||
|
const threadMessagesCollection = db.collections.get('thread_messages');
|
||||||
|
const successBatch = [];
|
||||||
|
const messageRecord = await msgCollection.find(id);
|
||||||
|
successBatch.push(
|
||||||
|
messageRecord.prepareUpdate((m) => {
|
||||||
|
m.status = status;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tmid) {
|
||||||
|
const threadMessageRecord = await threadMessagesCollection.find(id);
|
||||||
|
successBatch.push(
|
||||||
|
threadMessageRecord.prepareUpdate((tm) => {
|
||||||
|
tm.status = status;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.action(async() => {
|
||||||
|
await db.batch(...successBatch);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export async function sendMessageCall(message) {
|
export async function sendMessageCall(message) {
|
||||||
const {
|
const {
|
||||||
id: _id, subscription: { id: rid }, msg, tmid
|
id: _id, subscription: { id: rid }, msg, tmid
|
||||||
|
@ -17,30 +47,9 @@ export async function sendMessageCall(message) {
|
||||||
_id, rid, msg, tmid
|
_id, rid, msg, tmid
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
await changeMessageStatus(_id, tmid, messagesStatus.SENT);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const db = database.active;
|
await changeMessageStatus(_id, tmid, messagesStatus.ERROR);
|
||||||
const msgCollection = db.collections.get('messages');
|
|
||||||
const threadMessagesCollection = db.collections.get('thread_messages');
|
|
||||||
const errorBatch = [];
|
|
||||||
const messageRecord = await msgCollection.find(_id);
|
|
||||||
errorBatch.push(
|
|
||||||
messageRecord.prepareUpdate((m) => {
|
|
||||||
m.status = messagesStatus.ERROR;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tmid) {
|
|
||||||
const threadMessageRecord = await threadMessagesCollection.find(_id);
|
|
||||||
errorBatch.push(
|
|
||||||
threadMessageRecord.prepareUpdate((tm) => {
|
|
||||||
tm.status = messagesStatus.ERROR;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.action(async() => {
|
|
||||||
await db.batch(...errorBatch);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,17 +10,18 @@ import reduxStore from '../../createStore';
|
||||||
import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actions/usersTyping';
|
import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actions/usersTyping';
|
||||||
import debounce from '../../../utils/debounce';
|
import debounce from '../../../utils/debounce';
|
||||||
|
|
||||||
const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch(() => console.log('unsubscribeRoom')));
|
const unsubscribe = (subscriptions = []) => Promise.all(subscriptions.map(sub => sub.unsubscribe));
|
||||||
const removeListener = listener => listener.stop();
|
const removeListener = listener => listener.stop();
|
||||||
|
|
||||||
export default function subscribeRoom({ rid }) {
|
|
||||||
console.log(`[RCRN] Subscribed to room ${ rid }`);
|
|
||||||
let promises;
|
let promises;
|
||||||
let connectedListener;
|
let connectedListener;
|
||||||
let disconnectedListener;
|
let disconnectedListener;
|
||||||
let notifyRoomListener;
|
let notifyRoomListener;
|
||||||
let messageReceivedListener;
|
let messageReceivedListener;
|
||||||
|
|
||||||
|
export default function subscribeRoom({ rid }) {
|
||||||
|
console.log(`[RCRN] Subscribed to room ${ rid }`);
|
||||||
|
|
||||||
const handleConnection = () => {
|
const handleConnection = () => {
|
||||||
this.loadMissedMessages({ rid }).catch(e => console.log(e));
|
this.loadMissedMessages({ rid }).catch(e => console.log(e));
|
||||||
};
|
};
|
||||||
|
@ -197,25 +198,35 @@ export default function subscribeRoom({ rid }) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const stop = () => {
|
const stop = async() => {
|
||||||
|
let params;
|
||||||
if (promises) {
|
if (promises) {
|
||||||
promises.then(unsubscribe);
|
try {
|
||||||
|
params = await promises;
|
||||||
|
await unsubscribe(params);
|
||||||
|
} catch (error) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
promises = false;
|
promises = false;
|
||||||
}
|
}
|
||||||
if (connectedListener) {
|
if (connectedListener) {
|
||||||
connectedListener.then(removeListener);
|
params = await connectedListener;
|
||||||
|
removeListener(params);
|
||||||
connectedListener = false;
|
connectedListener = false;
|
||||||
}
|
}
|
||||||
if (disconnectedListener) {
|
if (disconnectedListener) {
|
||||||
disconnectedListener.then(removeListener);
|
params = await disconnectedListener;
|
||||||
|
removeListener(params);
|
||||||
disconnectedListener = false;
|
disconnectedListener = false;
|
||||||
}
|
}
|
||||||
if (notifyRoomListener) {
|
if (notifyRoomListener) {
|
||||||
notifyRoomListener.then(removeListener);
|
params = await notifyRoomListener;
|
||||||
|
removeListener(params);
|
||||||
notifyRoomListener = false;
|
notifyRoomListener = false;
|
||||||
}
|
}
|
||||||
if (messageReceivedListener) {
|
if (messageReceivedListener) {
|
||||||
messageReceivedListener.then(removeListener);
|
params = await messageReceivedListener;
|
||||||
|
removeListener(params);
|
||||||
messageReceivedListener = false;
|
messageReceivedListener = false;
|
||||||
}
|
}
|
||||||
reduxStore.dispatch(clearUserTyping());
|
reduxStore.dispatch(clearUserTyping());
|
||||||
|
@ -226,11 +237,7 @@ export default function subscribeRoom({ rid }) {
|
||||||
notifyRoomListener = this.sdk.onStreamData('stream-notify-room', handleNotifyRoomReceived);
|
notifyRoomListener = this.sdk.onStreamData('stream-notify-room', handleNotifyRoomReceived);
|
||||||
messageReceivedListener = this.sdk.onStreamData('stream-room-messages', handleMessageReceived);
|
messageReceivedListener = this.sdk.onStreamData('stream-room-messages', handleMessageReceived);
|
||||||
|
|
||||||
try {
|
|
||||||
promises = this.sdk.subscribeRoom(rid);
|
promises = this.sdk.subscribeRoom(rid);
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
stop: () => stop()
|
stop: () => stop()
|
||||||
|
|
|
@ -903,6 +903,8 @@ const RocketChat = {
|
||||||
name, custom, showButton = true, service
|
name, custom, showButton = true, service
|
||||||
} = services;
|
} = services;
|
||||||
|
|
||||||
|
const authName = name || service;
|
||||||
|
|
||||||
if (custom && showButton) {
|
if (custom && showButton) {
|
||||||
return 'oauth_custom';
|
return 'oauth_custom';
|
||||||
}
|
}
|
||||||
|
@ -916,8 +918,8 @@ const RocketChat = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove this after other oauth providers are implemented. e.g. Drupal, github_enterprise
|
// TODO: remove this after other oauth providers are implemented. e.g. Drupal, github_enterprise
|
||||||
const availableOAuth = ['facebook', 'github', 'gitlab', 'google', 'linkedin', 'meteor-developer', 'twitter'];
|
const availableOAuth = ['facebook', 'github', 'gitlab', 'google', 'linkedin', 'meteor-developer', 'twitter', 'wordpress'];
|
||||||
return availableOAuth.includes(name) ? 'oauth' : 'not_supported';
|
return availableOAuth.includes(authName) ? 'oauth' : 'not_supported';
|
||||||
},
|
},
|
||||||
getUsernameSuggestion() {
|
getUsernameSuggestion() {
|
||||||
// RC 0.65.0
|
// RC 0.65.0
|
||||||
|
@ -1098,6 +1100,23 @@ const RocketChat = {
|
||||||
},
|
},
|
||||||
translateMessage(message, targetLanguage) {
|
translateMessage(message, targetLanguage) {
|
||||||
return this.sdk.methodCall('autoTranslate.translateMessage', message, targetLanguage);
|
return this.sdk.methodCall('autoTranslate.translateMessage', message, targetLanguage);
|
||||||
|
},
|
||||||
|
getRoomTitle(room) {
|
||||||
|
const { UI_Use_Real_Name: useRealName } = reduxStore.getState().settings;
|
||||||
|
return ((room.prid || useRealName) && room.fname) || room.name;
|
||||||
|
},
|
||||||
|
|
||||||
|
findOrCreateInvite({ rid, days, maxUses }) {
|
||||||
|
// RC 2.4.0
|
||||||
|
return this.sdk.post('findOrCreateInvite', { rid, days, maxUses });
|
||||||
|
},
|
||||||
|
validateInviteToken(token) {
|
||||||
|
// RC 2.4.0
|
||||||
|
return this.sdk.post('validateInviteToken', { token });
|
||||||
|
},
|
||||||
|
useInviteToken(token) {
|
||||||
|
// RC 2.4.0
|
||||||
|
return this.sdk.post('useInviteToken', { token });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
export const formatAttachmentUrl = (attachmentUrl, userId, token, server) => (
|
export const formatAttachmentUrl = (attachmentUrl, userId, token, server) => {
|
||||||
encodeURI(attachmentUrl.includes('http') ? attachmentUrl : `${ server }${ attachmentUrl }?rc_uid=${ userId }&rc_token=${ token }`)
|
if (attachmentUrl.startsWith('http')) {
|
||||||
);
|
if (attachmentUrl.includes('rc_token')) {
|
||||||
|
return encodeURI(attachmentUrl);
|
||||||
|
}
|
||||||
|
return encodeURI(`${ attachmentUrl }?rc_uid=${ userId }&rc_token=${ token }`);
|
||||||
|
}
|
||||||
|
return encodeURI(`${ server }${ attachmentUrl }?rc_uid=${ userId }&rc_token=${ token }`);
|
||||||
|
};
|
||||||
|
|
|
@ -1,6 +1,17 @@
|
||||||
import NotificationsIOS from 'react-native-notifications';
|
import NotificationsIOS, { NotificationAction, NotificationCategory } from 'react-native-notifications';
|
||||||
|
|
||||||
import reduxStore from '../../lib/createStore';
|
import reduxStore from '../../lib/createStore';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
|
const replyAction = new NotificationAction({
|
||||||
|
activationMode: 'background',
|
||||||
|
title: I18n.t('Reply'),
|
||||||
|
textInput: {
|
||||||
|
buttonTitle: I18n.t('Reply'),
|
||||||
|
placeholder: I18n.t('Type_message')
|
||||||
|
},
|
||||||
|
identifier: 'REPLY_ACTION'
|
||||||
|
});
|
||||||
|
|
||||||
class PushNotification {
|
class PushNotification {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -20,7 +31,12 @@ class PushNotification {
|
||||||
completion();
|
completion();
|
||||||
});
|
});
|
||||||
|
|
||||||
NotificationsIOS.requestPermissions();
|
const actions = [];
|
||||||
|
actions.push(new NotificationCategory({
|
||||||
|
identifier: 'MESSAGE',
|
||||||
|
actions: [replyAction]
|
||||||
|
}));
|
||||||
|
NotificationsIOS.requestPermissions(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceToken() {
|
getDeviceToken() {
|
||||||
|
|
|
@ -0,0 +1,442 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
PanGestureHandler,
|
||||||
|
State,
|
||||||
|
PinchGestureHandler
|
||||||
|
} from 'react-native-gesture-handler';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
import Animated, { Easing } from 'react-native-reanimated';
|
||||||
|
import { ResponsiveComponent } from 'react-native-responsive-ui';
|
||||||
|
|
||||||
|
const AnimatedFastImage = Animated.createAnimatedComponent(FastImage);
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
wrapper: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
set,
|
||||||
|
cond,
|
||||||
|
eq,
|
||||||
|
or,
|
||||||
|
add,
|
||||||
|
sub,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
multiply,
|
||||||
|
divide,
|
||||||
|
lessThan,
|
||||||
|
decay,
|
||||||
|
timing,
|
||||||
|
diff,
|
||||||
|
not,
|
||||||
|
abs,
|
||||||
|
startClock,
|
||||||
|
stopClock,
|
||||||
|
clockRunning,
|
||||||
|
Value,
|
||||||
|
Clock,
|
||||||
|
event
|
||||||
|
} = Animated;
|
||||||
|
|
||||||
|
function scaleDiff(value) {
|
||||||
|
const tmp = new Value(1);
|
||||||
|
const prev = new Value(1);
|
||||||
|
return [set(tmp, divide(value, prev)), set(prev, value), tmp];
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragDiff(value, updating) {
|
||||||
|
const tmp = new Value(0);
|
||||||
|
const prev = new Value(0);
|
||||||
|
return cond(
|
||||||
|
updating,
|
||||||
|
[set(tmp, sub(value, prev)), set(prev, value), tmp],
|
||||||
|
set(prev, 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns linear friction coeff. When `value` is 0 coeff is 1 (no friction), then
|
||||||
|
// it grows linearly until it reaches `MAX_FRICTION` when `value` is equal
|
||||||
|
// to `MAX_VALUE`
|
||||||
|
function friction(value) {
|
||||||
|
const MAX_FRICTION = 5;
|
||||||
|
const MAX_VALUE = 100;
|
||||||
|
return max(
|
||||||
|
1,
|
||||||
|
min(MAX_FRICTION, add(1, multiply(value, (MAX_FRICTION - 1) / MAX_VALUE)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function speed(value) {
|
||||||
|
const clock = new Clock();
|
||||||
|
const dt = diff(clock);
|
||||||
|
return cond(lessThan(dt, 1), 0, multiply(1000, divide(diff(value), dt)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const MIN_SCALE = 1;
|
||||||
|
const MAX_SCALE = 2;
|
||||||
|
|
||||||
|
function scaleRest(value) {
|
||||||
|
return cond(
|
||||||
|
lessThan(value, MIN_SCALE),
|
||||||
|
MIN_SCALE,
|
||||||
|
cond(lessThan(MAX_SCALE, value), MAX_SCALE, value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scaleFriction(value, rest, delta) {
|
||||||
|
const MAX_FRICTION = 20;
|
||||||
|
const MAX_VALUE = 0.5;
|
||||||
|
const res = multiply(value, delta);
|
||||||
|
const howFar = abs(sub(rest, value));
|
||||||
|
const f = max(
|
||||||
|
1,
|
||||||
|
min(MAX_FRICTION, add(1, multiply(howFar, (MAX_FRICTION - 1) / MAX_VALUE)))
|
||||||
|
);
|
||||||
|
return cond(
|
||||||
|
lessThan(0, howFar),
|
||||||
|
multiply(value, add(1, divide(add(delta, -1), f))),
|
||||||
|
res
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTiming(clock, value, dest, startStopClock = true) {
|
||||||
|
const state = {
|
||||||
|
finished: new Value(0),
|
||||||
|
position: new Value(0),
|
||||||
|
frameTime: new Value(0),
|
||||||
|
time: new Value(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
toValue: new Value(0),
|
||||||
|
duration: 300,
|
||||||
|
easing: Easing.inOut(Easing.cubic)
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
cond(clockRunning(clock), 0, [
|
||||||
|
set(state.finished, 0),
|
||||||
|
set(state.frameTime, 0),
|
||||||
|
set(state.time, 0),
|
||||||
|
set(state.position, value),
|
||||||
|
set(config.toValue, dest),
|
||||||
|
startStopClock && startClock(clock)
|
||||||
|
]),
|
||||||
|
timing(clock, state, config),
|
||||||
|
cond(state.finished, startStopClock && stopClock(clock)),
|
||||||
|
state.position
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function runDecay(clock, value, velocity) {
|
||||||
|
const state = {
|
||||||
|
finished: new Value(0),
|
||||||
|
velocity: new Value(0),
|
||||||
|
position: new Value(0),
|
||||||
|
time: new Value(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = { deceleration: 0.99 };
|
||||||
|
|
||||||
|
return [
|
||||||
|
cond(clockRunning(clock), 0, [
|
||||||
|
set(state.finished, 0),
|
||||||
|
set(state.velocity, velocity),
|
||||||
|
set(state.position, value),
|
||||||
|
set(state.time, 0),
|
||||||
|
startClock(clock)
|
||||||
|
]),
|
||||||
|
set(state.position, value),
|
||||||
|
decay(clock, state, config),
|
||||||
|
cond(state.finished, stopClock(clock)),
|
||||||
|
state.position
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function bouncyPinch(
|
||||||
|
value,
|
||||||
|
gesture,
|
||||||
|
gestureActive,
|
||||||
|
focalX,
|
||||||
|
displacementX,
|
||||||
|
focalY,
|
||||||
|
displacementY
|
||||||
|
) {
|
||||||
|
const clock = new Clock();
|
||||||
|
|
||||||
|
const delta = scaleDiff(gesture);
|
||||||
|
const rest = scaleRest(value);
|
||||||
|
const focalXRest = cond(
|
||||||
|
lessThan(value, 1),
|
||||||
|
0,
|
||||||
|
sub(displacementX, multiply(focalX, add(-1, divide(rest, value))))
|
||||||
|
);
|
||||||
|
const focalYRest = cond(
|
||||||
|
lessThan(value, 1),
|
||||||
|
0,
|
||||||
|
sub(displacementY, multiply(focalY, add(-1, divide(rest, value))))
|
||||||
|
);
|
||||||
|
const nextScale = new Value(1);
|
||||||
|
|
||||||
|
return cond(
|
||||||
|
[delta, gestureActive],
|
||||||
|
[
|
||||||
|
stopClock(clock),
|
||||||
|
set(nextScale, scaleFriction(value, rest, delta)),
|
||||||
|
set(
|
||||||
|
displacementX,
|
||||||
|
sub(displacementX, multiply(focalX, add(-1, divide(nextScale, value))))
|
||||||
|
),
|
||||||
|
set(
|
||||||
|
displacementY,
|
||||||
|
sub(displacementY, multiply(focalY, add(-1, divide(nextScale, value))))
|
||||||
|
),
|
||||||
|
nextScale
|
||||||
|
],
|
||||||
|
cond(
|
||||||
|
or(clockRunning(clock), not(eq(rest, value))),
|
||||||
|
[
|
||||||
|
set(displacementX, runTiming(clock, displacementX, focalXRest, false)),
|
||||||
|
set(displacementY, runTiming(clock, displacementY, focalYRest, false)),
|
||||||
|
runTiming(clock, value, rest)
|
||||||
|
],
|
||||||
|
value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bouncy(
|
||||||
|
value,
|
||||||
|
gestureDiv,
|
||||||
|
gestureActive,
|
||||||
|
lowerBound,
|
||||||
|
upperBound,
|
||||||
|
f
|
||||||
|
) {
|
||||||
|
const timingClock = new Clock();
|
||||||
|
const decayClock = new Clock();
|
||||||
|
|
||||||
|
const velocity = speed(value);
|
||||||
|
|
||||||
|
// did value go beyond the limits (lower, upper)
|
||||||
|
const isOutOfBounds = or(
|
||||||
|
lessThan(value, lowerBound),
|
||||||
|
lessThan(upperBound, value)
|
||||||
|
);
|
||||||
|
// position to snap to (upper or lower is beyond or the current value elsewhere)
|
||||||
|
const rest = cond(
|
||||||
|
lessThan(value, lowerBound),
|
||||||
|
lowerBound,
|
||||||
|
cond(lessThan(upperBound, value), upperBound, value)
|
||||||
|
);
|
||||||
|
// how much the value exceeds the bounds, this is used to calculate friction
|
||||||
|
const outOfBounds = abs(sub(rest, value));
|
||||||
|
|
||||||
|
return cond(
|
||||||
|
[gestureDiv, velocity, gestureActive],
|
||||||
|
[
|
||||||
|
stopClock(timingClock),
|
||||||
|
stopClock(decayClock),
|
||||||
|
add(value, divide(gestureDiv, f(outOfBounds)))
|
||||||
|
],
|
||||||
|
cond(
|
||||||
|
or(clockRunning(timingClock), isOutOfBounds),
|
||||||
|
[stopClock(decayClock), runTiming(timingClock, value, rest)],
|
||||||
|
cond(
|
||||||
|
or(clockRunning(decayClock), lessThan(5, abs(velocity))),
|
||||||
|
runDecay(decayClock, value, velocity),
|
||||||
|
value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const WIDTH = 300;
|
||||||
|
const HEIGHT = 300;
|
||||||
|
|
||||||
|
// it was picked from https://github.com/software-mansion/react-native-reanimated/tree/master/Example/imageViewer
|
||||||
|
// and changed to use FastImage animated component
|
||||||
|
class ImageViewer extends ResponsiveComponent {
|
||||||
|
pinchRef = React.createRef();
|
||||||
|
|
||||||
|
panRef = React.createRef();
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
uri: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// DECLARE TRANSX
|
||||||
|
const panTransX = new Value(0);
|
||||||
|
const panTransY = new Value(0);
|
||||||
|
|
||||||
|
// PINCH
|
||||||
|
const pinchScale = new Value(1);
|
||||||
|
const pinchFocalX = new Value(0);
|
||||||
|
const pinchFocalY = new Value(0);
|
||||||
|
const pinchState = new Value(-1);
|
||||||
|
|
||||||
|
this._onPinchEvent = event([
|
||||||
|
{
|
||||||
|
nativeEvent: {
|
||||||
|
state: pinchState,
|
||||||
|
scale: pinchScale,
|
||||||
|
focalX: pinchFocalX,
|
||||||
|
focalY: pinchFocalY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// SCALE
|
||||||
|
const scale = new Value(1);
|
||||||
|
const pinchActive = eq(pinchState, State.ACTIVE);
|
||||||
|
this._focalDisplacementX = new Value(0);
|
||||||
|
const relativeFocalX = sub(
|
||||||
|
pinchFocalX,
|
||||||
|
add(panTransX, this._focalDisplacementX)
|
||||||
|
);
|
||||||
|
this._focalDisplacementY = new Value(0);
|
||||||
|
const relativeFocalY = sub(
|
||||||
|
pinchFocalY,
|
||||||
|
add(panTransY, this._focalDisplacementY)
|
||||||
|
);
|
||||||
|
this._scale = set(
|
||||||
|
scale,
|
||||||
|
bouncyPinch(
|
||||||
|
scale,
|
||||||
|
pinchScale,
|
||||||
|
pinchActive,
|
||||||
|
relativeFocalX,
|
||||||
|
this._focalDisplacementX,
|
||||||
|
relativeFocalY,
|
||||||
|
this._focalDisplacementY
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// PAN
|
||||||
|
const dragX = new Value(0);
|
||||||
|
const dragY = new Value(0);
|
||||||
|
const panState = new Value(-1);
|
||||||
|
this._onPanEvent = event([
|
||||||
|
{
|
||||||
|
nativeEvent: {
|
||||||
|
translationX: dragX,
|
||||||
|
translationY: dragY,
|
||||||
|
state: panState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const panActive = eq(panState, State.ACTIVE);
|
||||||
|
const panFriction = value => friction(value);
|
||||||
|
|
||||||
|
// X
|
||||||
|
const panUpX = cond(
|
||||||
|
lessThan(this._scale, 1),
|
||||||
|
0,
|
||||||
|
multiply(-1, this._focalDisplacementX)
|
||||||
|
);
|
||||||
|
const panLowX = add(panUpX, multiply(-WIDTH, add(max(1, this._scale), -1)));
|
||||||
|
this._panTransX = set(
|
||||||
|
panTransX,
|
||||||
|
bouncy(
|
||||||
|
panTransX,
|
||||||
|
dragDiff(dragX, panActive),
|
||||||
|
or(panActive, pinchActive),
|
||||||
|
panLowX,
|
||||||
|
panUpX,
|
||||||
|
panFriction
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Y
|
||||||
|
const panUpY = cond(
|
||||||
|
lessThan(this._scale, 1),
|
||||||
|
0,
|
||||||
|
multiply(-1, this._focalDisplacementY)
|
||||||
|
);
|
||||||
|
const panLowY = add(
|
||||||
|
panUpY,
|
||||||
|
multiply(-HEIGHT, add(max(1, this._scale), -1))
|
||||||
|
);
|
||||||
|
this._panTransY = set(
|
||||||
|
panTransY,
|
||||||
|
bouncy(
|
||||||
|
panTransY,
|
||||||
|
dragDiff(dragY, panActive),
|
||||||
|
or(panActive, pinchActive),
|
||||||
|
panLowY,
|
||||||
|
panUpY,
|
||||||
|
panFriction
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { uri, ...props } = this.props;
|
||||||
|
const { width } = this.state.window;
|
||||||
|
|
||||||
|
// The below two animated values makes it so that scale appears to be done
|
||||||
|
// from the top left corner of the image view instead of its center. This
|
||||||
|
// is required for the "scale focal point" math to work correctly
|
||||||
|
const scaleTopLeftFixX = divide(multiply(WIDTH, add(this._scale, -1)), 2);
|
||||||
|
const scaleTopLeftFixY = divide(multiply(HEIGHT, add(this._scale, -1)), 2);
|
||||||
|
return (
|
||||||
|
<View style={styles.wrapper}>
|
||||||
|
<PinchGestureHandler
|
||||||
|
ref={this.pinchRef}
|
||||||
|
simultaneousHandlers={this.panRef}
|
||||||
|
onGestureEvent={this._onPinchEvent}
|
||||||
|
onHandlerStateChange={this._onPinchEvent}
|
||||||
|
>
|
||||||
|
<Animated.View>
|
||||||
|
<PanGestureHandler
|
||||||
|
ref={this.panRef}
|
||||||
|
minDist={10}
|
||||||
|
avgTouches
|
||||||
|
simultaneousHandlers={this.pinchRef}
|
||||||
|
onGestureEvent={this._onPanEvent}
|
||||||
|
onHandlerStateChange={this._onPanEvent}
|
||||||
|
>
|
||||||
|
<AnimatedFastImage
|
||||||
|
style={[
|
||||||
|
styles.image,
|
||||||
|
{
|
||||||
|
width,
|
||||||
|
height: '100%'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
transform: [
|
||||||
|
{ translateX: this._panTransX },
|
||||||
|
{ translateY: this._panTransY },
|
||||||
|
{ translateX: this._focalDisplacementX },
|
||||||
|
{ translateY: this._focalDisplacementY },
|
||||||
|
{ translateX: scaleTopLeftFixX },
|
||||||
|
{ translateY: scaleTopLeftFixY },
|
||||||
|
{ scale: this._scale }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
resizeMode='contain'
|
||||||
|
source={{ uri }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PanGestureHandler>
|
||||||
|
</Animated.View>
|
||||||
|
</PinchGestureHandler>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageViewer;
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { ScrollView, StyleSheet } from 'react-native';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
scrollContent: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%'
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ImageViewer = ({
|
||||||
|
uri, ...props
|
||||||
|
}) => (
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.scrollContent}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
maximumZoomScale={2}
|
||||||
|
>
|
||||||
|
<FastImage
|
||||||
|
style={styles.image}
|
||||||
|
resizeMode='contain'
|
||||||
|
source={{ uri }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
|
||||||
|
ImageViewer.propTypes = {
|
||||||
|
uri: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageViewer;
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shortnameToUnicode } from 'emoji-toolkit';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
@ -7,6 +6,7 @@ import I18n from '../../i18n';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import Markdown from '../../containers/markdown';
|
import Markdown from '../../containers/markdown';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
import shortnameToUnicode from '../../utils/shortnameToUnicode';
|
||||||
|
|
||||||
const formatMsg = ({
|
const formatMsg = ({
|
||||||
lastMessage, type, showLastMessage, username
|
lastMessage, type, showLastMessage, username
|
||||||
|
@ -14,7 +14,7 @@ const formatMsg = ({
|
||||||
if (!showLastMessage) {
|
if (!showLastMessage) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (!lastMessage || lastMessage.pinned) {
|
if (!lastMessage || !lastMessage.u || lastMessage.pinned) {
|
||||||
return I18n.t('No_Message');
|
return I18n.t('No_Message');
|
||||||
}
|
}
|
||||||
if (lastMessage.t === 'jitsi_call_started') {
|
if (lastMessage.t === 'jitsi_call_started') {
|
||||||
|
|
|
@ -5,9 +5,7 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import styles, {
|
import styles, { ROW_HEIGHT } from './styles';
|
||||||
ROW_HEIGHT
|
|
||||||
} from './styles';
|
|
||||||
import UnreadBadge from './UnreadBadge';
|
import UnreadBadge from './UnreadBadge';
|
||||||
import TypeIcon from './TypeIcon';
|
import TypeIcon from './TypeIcon';
|
||||||
import LastMessage from './LastMessage';
|
import LastMessage from './LastMessage';
|
||||||
|
|
|
@ -15,6 +15,7 @@ import crashReport from './crashReport';
|
||||||
import customEmojis from './customEmojis';
|
import customEmojis from './customEmojis';
|
||||||
import activeUsers from './activeUsers';
|
import activeUsers from './activeUsers';
|
||||||
import usersTyping from './usersTyping';
|
import usersTyping from './usersTyping';
|
||||||
|
import inviteLinks from './inviteLinks';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings,
|
settings,
|
||||||
|
@ -32,5 +33,6 @@ export default combineReducers({
|
||||||
crashReport,
|
crashReport,
|
||||||
customEmojis,
|
customEmojis,
|
||||||
activeUsers,
|
activeUsers,
|
||||||
usersTyping
|
usersTyping,
|
||||||
|
inviteLinks
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { INVITE_LINKS } from '../actions/actionsTypes';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
token: '',
|
||||||
|
days: 1,
|
||||||
|
maxUses: 0,
|
||||||
|
invite: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (state = initialState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case INVITE_LINKS.SET_TOKEN:
|
||||||
|
return {
|
||||||
|
token: action.token
|
||||||
|
};
|
||||||
|
case INVITE_LINKS.SET_PARAMS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.params
|
||||||
|
};
|
||||||
|
case INVITE_LINKS.SET_INVITE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
invite: action.invite
|
||||||
|
};
|
||||||
|
case INVITE_LINKS.REQUEST:
|
||||||
|
return state;
|
||||||
|
case INVITE_LINKS.SUCCESS:
|
||||||
|
return initialState;
|
||||||
|
case INVITE_LINKS.FAILURE:
|
||||||
|
return initialState;
|
||||||
|
case INVITE_LINKS.CLEAR:
|
||||||
|
return initialState;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -6,16 +6,27 @@ import RNUserDefaults from 'rn-user-defaults';
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import { selectServerRequest } from '../actions/server';
|
import { selectServerRequest } from '../actions/server';
|
||||||
|
import { inviteLinksSetToken, inviteLinksRequest } from '../actions/inviteLinks';
|
||||||
import database from '../lib/database';
|
import database from '../lib/database';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import EventEmitter from '../utils/events';
|
import EventEmitter from '../utils/events';
|
||||||
import { appStart } from '../actions';
|
import { appStart } from '../actions';
|
||||||
import { isIOS } from '../utils/deviceInfo';
|
|
||||||
|
|
||||||
const roomTypes = {
|
const roomTypes = {
|
||||||
channel: 'c', direct: 'd', group: 'p'
|
channel: 'c', direct: 'd', group: 'p'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleInviteLink = function* handleInviteLink({ params, requireLogin = false }) {
|
||||||
|
if (params.path && params.path.startsWith('invite/')) {
|
||||||
|
const token = params.path.replace('invite/', '');
|
||||||
|
if (requireLogin) {
|
||||||
|
yield put(inviteLinksSetToken(token));
|
||||||
|
} else {
|
||||||
|
yield put(inviteLinksRequest(token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const navigate = function* navigate({ params }) {
|
const navigate = function* navigate({ params }) {
|
||||||
yield put(appStart('inside'));
|
yield put(appStart('inside'));
|
||||||
if (params.rid) {
|
if (params.rid) {
|
||||||
|
@ -25,6 +36,8 @@ const navigate = function* navigate({ params }) {
|
||||||
yield Navigation.navigate('RoomsListView');
|
yield Navigation.navigate('RoomsListView');
|
||||||
Navigation.navigate('RoomView', { rid: params.rid, name, t: roomTypes[type] });
|
Navigation.navigate('RoomView', { rid: params.rid, name, t: roomTypes[type] });
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
yield handleInviteLink({ params });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,10 +46,6 @@ const handleOpen = function* handleOpen({ params }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isIOS) {
|
|
||||||
yield RNUserDefaults.setName('group.ios.chat.rocket');
|
|
||||||
}
|
|
||||||
|
|
||||||
let { host } = params;
|
let { host } = params;
|
||||||
if (!/^(http|https)/.test(host)) {
|
if (!/^(http|https)/.test(host)) {
|
||||||
host = `https://${ params.host }`;
|
host = `https://${ params.host }`;
|
||||||
|
@ -68,7 +77,7 @@ const handleOpen = function* handleOpen({ params }) {
|
||||||
const servers = yield serversCollection.find(host);
|
const servers = yield serversCollection.find(host);
|
||||||
if (servers && user) {
|
if (servers && user) {
|
||||||
yield put(selectServerRequest(host));
|
yield put(selectServerRequest(host));
|
||||||
yield take(types.SERVER.SELECT_SUCCESS);
|
yield take(types.LOGIN.SUCCESS);
|
||||||
yield navigate({ params });
|
yield navigate({ params });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -87,10 +96,9 @@ const handleOpen = function* handleOpen({ params }) {
|
||||||
if (params.token) {
|
if (params.token) {
|
||||||
yield take(types.SERVER.SELECT_SUCCESS);
|
yield take(types.SERVER.SELECT_SUCCESS);
|
||||||
yield RocketChat.connect({ server: host, user: { token: params.token } });
|
yield RocketChat.connect({ server: host, user: { token: params.token } });
|
||||||
|
} else {
|
||||||
|
yield handleInviteLink({ params, requireLogin: true });
|
||||||
}
|
}
|
||||||
Navigation.navigate('OnboardingView', { previousServer: server });
|
|
||||||
yield delay(1000);
|
|
||||||
EventEmitter.emit('NewServer', { server: host });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import createChannel from './createChannel';
|
||||||
import init from './init';
|
import init from './init';
|
||||||
import state from './state';
|
import state from './state';
|
||||||
import deepLinking from './deepLinking';
|
import deepLinking from './deepLinking';
|
||||||
|
import inviteLinks from './inviteLinks';
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield all([
|
yield all([
|
||||||
|
@ -19,7 +20,8 @@ const root = function* root() {
|
||||||
messages(),
|
messages(),
|
||||||
selectServer(),
|
selectServer(),
|
||||||
state(),
|
state(),
|
||||||
deepLinking()
|
deepLinking(),
|
||||||
|
inviteLinks()
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { AsyncStorage } from 'react-native';
|
import { AsyncStorage } from 'react-native';
|
||||||
import { put, takeLatest, all } from 'redux-saga/effects';
|
import { put, takeLatest, all } from 'redux-saga/effects';
|
||||||
import SplashScreen from 'react-native-splash-screen';
|
|
||||||
import RNUserDefaults from 'rn-user-defaults';
|
import RNUserDefaults from 'rn-user-defaults';
|
||||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
import RNBootSplash from 'react-native-bootsplash';
|
||||||
|
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
import { selectServerRequest } from '../actions/server';
|
import { selectServerRequest } from '../actions/server';
|
||||||
|
@ -94,9 +94,6 @@ const restore = function* restore() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowCrashReport = yield RocketChat.getAllowCrashReport();
|
|
||||||
yield put(toggleCrashReport(allowCrashReport));
|
|
||||||
|
|
||||||
if (!token || !server) {
|
if (!token || !server) {
|
||||||
yield all([
|
yield all([
|
||||||
RNUserDefaults.clear(RocketChat.TOKEN_KEY),
|
RNUserDefaults.clear(RocketChat.TOKEN_KEY),
|
||||||
|
@ -125,7 +122,7 @@ const start = function* start({ root }) {
|
||||||
} else if (root === 'outside') {
|
} else if (root === 'outside') {
|
||||||
yield Navigation.navigate('OutsideStack');
|
yield Navigation.navigate('OutsideStack');
|
||||||
}
|
}
|
||||||
SplashScreen.hide();
|
RNBootSplash.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import {
|
||||||
|
put, takeLatest, delay, select
|
||||||
|
} from 'redux-saga/effects';
|
||||||
|
import { Alert } from 'react-native';
|
||||||
|
|
||||||
|
import { INVITE_LINKS } from '../actions/actionsTypes';
|
||||||
|
import { inviteLinksSuccess, inviteLinksFailure, inviteLinksSetInvite } from '../actions/inviteLinks';
|
||||||
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import log from '../utils/log';
|
||||||
|
import Navigation from '../lib/Navigation';
|
||||||
|
import I18n from '../i18n';
|
||||||
|
|
||||||
|
const handleRequest = function* handleRequest({ token }) {
|
||||||
|
try {
|
||||||
|
const validateResult = yield RocketChat.validateInviteToken(token);
|
||||||
|
if (!validateResult.valid) {
|
||||||
|
yield put(inviteLinksFailure());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = yield RocketChat.useInviteToken(token);
|
||||||
|
if (!result.success) {
|
||||||
|
yield put(inviteLinksFailure());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.room && result.room.rid) {
|
||||||
|
yield delay(1000);
|
||||||
|
yield Navigation.navigate('RoomsListView');
|
||||||
|
const { room } = result;
|
||||||
|
Navigation.navigate('RoomView', {
|
||||||
|
rid: room.rid,
|
||||||
|
name: RocketChat.getRoomTitle(room),
|
||||||
|
t: room.t
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
yield put(inviteLinksSuccess());
|
||||||
|
} catch (e) {
|
||||||
|
yield put(inviteLinksFailure());
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFailure = function handleFailure() {
|
||||||
|
Alert.alert(I18n.t('Oops'), I18n.t('Invalid_or_expired_invite_token'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateInviteLink = function* handleCreateInviteLink({ rid }) {
|
||||||
|
try {
|
||||||
|
const inviteLinks = yield select(state => state.inviteLinks);
|
||||||
|
const result = yield RocketChat.findOrCreateInvite({
|
||||||
|
rid, days: inviteLinks.days, maxUses: inviteLinks.maxUses
|
||||||
|
});
|
||||||
|
if (!result.success) {
|
||||||
|
Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_invite') }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield put(inviteLinksSetInvite(result));
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const root = function* root() {
|
||||||
|
yield takeLatest(INVITE_LINKS.REQUEST, handleRequest);
|
||||||
|
yield takeLatest(INVITE_LINKS.FAILURE, handleFailure);
|
||||||
|
yield takeLatest(INVITE_LINKS.CREATE, handleCreateInviteLink);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default root;
|
|
@ -20,6 +20,7 @@ import I18n from '../i18n';
|
||||||
import database from '../lib/database';
|
import database from '../lib/database';
|
||||||
import EventEmitter from '../utils/events';
|
import EventEmitter from '../utils/events';
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
|
import { inviteLinksRequest } from '../actions/inviteLinks';
|
||||||
|
|
||||||
const getServer = state => state.server.server;
|
const getServer = state => state.server.server;
|
||||||
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
|
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
|
||||||
|
@ -111,20 +112,31 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
yield RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ server }`, user.id);
|
yield RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ server }`, user.id);
|
||||||
|
yield RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ user.id }`, user.token);
|
||||||
yield put(setUser(user));
|
yield put(setUser(user));
|
||||||
EventEmitter.emit('connected');
|
EventEmitter.emit('connected');
|
||||||
|
|
||||||
|
let currentRoot;
|
||||||
if (!user.username) {
|
if (!user.username) {
|
||||||
yield put(appStart('setUsername'));
|
yield put(appStart('setUsername'));
|
||||||
} else if (adding) {
|
} else if (adding) {
|
||||||
yield put(serverFinishAdd());
|
yield put(serverFinishAdd());
|
||||||
yield put(appStart('inside'));
|
yield put(appStart('inside'));
|
||||||
} else {
|
} else {
|
||||||
const currentRoot = yield select(state => state.app.root);
|
currentRoot = yield select(state => state.app.root);
|
||||||
if (currentRoot !== 'inside') {
|
if (currentRoot !== 'inside') {
|
||||||
yield put(appStart('inside'));
|
yield put(appStart('inside'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// after a successful login, check if it's been invited via invite link
|
||||||
|
currentRoot = yield select(state => state.app.root);
|
||||||
|
if (currentRoot === 'inside') {
|
||||||
|
const inviteLinkToken = yield select(state => state.inviteLinks.token);
|
||||||
|
if (inviteLinkToken) {
|
||||||
|
yield put(inviteLinksRequest(inviteLinkToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,18 @@ import database from '../lib/database';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms';
|
import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
// import buildMessage from '../lib/methods/helpers/buildMessage';
|
|
||||||
|
const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) {
|
||||||
|
const serversDB = database.servers;
|
||||||
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
|
const serverRecord = yield serversCollection.find(server);
|
||||||
|
|
||||||
|
return serversDB.action(async() => {
|
||||||
|
await serverRecord.update((record) => {
|
||||||
|
record.roomsUpdatedAt = newRoomsUpdatedAt;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleRoomsRequest = function* handleRoomsRequest() {
|
const handleRoomsRequest = function* handleRoomsRequest() {
|
||||||
try {
|
try {
|
||||||
|
@ -26,28 +37,15 @@ const handleRoomsRequest = function* handleRoomsRequest() {
|
||||||
const { subscriptions } = mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
|
const { subscriptions } = mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
|
||||||
|
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
yield db.action(async() => {
|
|
||||||
if (!subscriptions.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subCollection = db.collections.get('subscriptions');
|
const subCollection = db.collections.get('subscriptions');
|
||||||
// const messagesCollection = db.collections.get('messages');
|
|
||||||
|
|
||||||
|
if (subscriptions.length) {
|
||||||
const subsIds = subscriptions.map(sub => sub.rid);
|
const subsIds = subscriptions.map(sub => sub.rid);
|
||||||
const existingSubs = await subCollection.query(Q.where('id', Q.oneOf(subsIds))).fetch();
|
const existingSubs = yield subCollection.query(Q.where('id', Q.oneOf(subsIds))).fetch();
|
||||||
const subsToUpdate = existingSubs.filter(i1 => subscriptions.find(i2 => i1._id === i2._id));
|
const subsToUpdate = existingSubs.filter(i1 => subscriptions.find(i2 => i1._id === i2._id));
|
||||||
const subsToCreate = subscriptions.filter(i1 => !existingSubs.find(i2 => i1._id === i2._id));
|
const subsToCreate = subscriptions.filter(i1 => !existingSubs.find(i2 => i1._id === i2._id));
|
||||||
// TODO: subsToDelete?
|
// TODO: subsToDelete?
|
||||||
|
|
||||||
// const lastMessages = subscriptions
|
|
||||||
// .map(sub => sub.lastMessage && buildMessage(sub.lastMessage))
|
|
||||||
// .filter(lm => lm);
|
|
||||||
// const lastMessagesIds = lastMessages.map(lm => lm._id);
|
|
||||||
// const existingMessages = await messagesCollection.query(Q.where('id', Q.oneOf(lastMessagesIds))).fetch();
|
|
||||||
// const messagesToUpdate = existingMessages.filter(i1 => lastMessages.find(i2 => i1.id === i2._id));
|
|
||||||
// const messagesToCreate = lastMessages.filter(i1 => !existingMessages.find(i2 => i1._id === i2.id));
|
|
||||||
|
|
||||||
const allRecords = [
|
const allRecords = [
|
||||||
...subsToCreate.map(subscription => subCollection.prepareCreate((s) => {
|
...subsToCreate.map(subscription => subCollection.prepareCreate((s) => {
|
||||||
s._raw = sanitizedRaw({ id: subscription.rid }, subCollection.schema);
|
s._raw = sanitizedRaw({ id: subscription.rid }, subCollection.schema);
|
||||||
|
@ -59,37 +57,14 @@ const handleRoomsRequest = function* handleRoomsRequest() {
|
||||||
Object.assign(subscription, newSub);
|
Object.assign(subscription, newSub);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
// ...messagesToCreate.map(message => messagesCollection.prepareCreate((m) => {
|
|
||||||
// m._raw = sanitizedRaw({ id: message._id }, messagesCollection.schema);
|
|
||||||
// m.subscription.id = message.rid;
|
|
||||||
// return Object.assign(m, message);
|
|
||||||
// })),
|
|
||||||
// ...messagesToUpdate.map((message) => {
|
|
||||||
// const newMessage = lastMessages.find(m => m._id === message.id);
|
|
||||||
// return message.prepareUpdate(() => {
|
|
||||||
// Object.assign(message, newMessage);
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
yield db.action(async() => {
|
||||||
await db.batch(...allRecords);
|
await db.batch(...allRecords);
|
||||||
} catch (e) {
|
});
|
||||||
log(e);
|
|
||||||
}
|
}
|
||||||
return allRecords.length;
|
|
||||||
});
|
|
||||||
|
|
||||||
yield serversDB.action(async() => {
|
|
||||||
try {
|
|
||||||
await serverRecord.update((record) => {
|
|
||||||
record.roomsUpdatedAt = newRoomsUpdatedAt;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
yield updateRooms({ server, newRoomsUpdatedAt });
|
||||||
yield put(roomsSuccess());
|
yield put(roomsSuccess());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(roomsFailure(e));
|
yield put(roomsFailure(e));
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
import { Alert } from 'react-native';
|
import { Alert } from 'react-native';
|
||||||
import RNUserDefaults from 'rn-user-defaults';
|
import RNUserDefaults from 'rn-user-defaults';
|
||||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
import semver from 'semver';
|
||||||
|
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
import { SERVER } from '../actions/actionsTypes';
|
import { SERVER } from '../actions/actionsTypes';
|
||||||
|
@ -35,18 +36,20 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const validVersion = semver.coerce(serverInfo.version);
|
||||||
|
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
const serversCollection = serversDB.collections.get('servers');
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
yield serversDB.action(async() => {
|
yield serversDB.action(async() => {
|
||||||
try {
|
try {
|
||||||
const serverRecord = await serversCollection.find(server);
|
const serverRecord = await serversCollection.find(server);
|
||||||
await serverRecord.update((record) => {
|
await serverRecord.update((record) => {
|
||||||
record.version = serverInfo.version;
|
record.version = validVersion;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await serversCollection.create((record) => {
|
await serversCollection.create((record) => {
|
||||||
record._raw = sanitizedRaw({ id: server }, serversCollection.schema);
|
record._raw = sanitizedRaw({ id: server }, serversCollection.schema);
|
||||||
record.version = serverInfo.version;
|
record.version = validVersion;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -100,8 +103,6 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
||||||
RocketChat.setSettings();
|
RocketChat.setSettings();
|
||||||
RocketChat.setCustomEmojis();
|
RocketChat.setCustomEmojis();
|
||||||
|
|
||||||
yield RocketChat.setCustomEmojis();
|
|
||||||
|
|
||||||
let serverInfo;
|
let serverInfo;
|
||||||
if (fetchVersion) {
|
if (fetchVersion) {
|
||||||
serverInfo = yield getServerInfo({ server, raiseError: false });
|
serverInfo = yield getServerInfo({ server, raiseError: false });
|
||||||
|
|
|
@ -50,6 +50,11 @@ export const initTabletNav = (setState) => {
|
||||||
setState({ showModal: true });
|
setState({ showModal: true });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (routeName === 'AttachmentView') {
|
||||||
|
modalRef.dispatch(NavigationActions.navigate({ routeName, params }));
|
||||||
|
setState({ showModal: true });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (action.type === 'Navigation/RESET' && isSplited()) {
|
if (action.type === 'Navigation/RESET' && isSplited()) {
|
||||||
const { params } = action.actions[action.index];
|
const { params } = action.actions[action.index];
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
import DeviceInfo from 'react-native-device-info';
|
import DeviceInfo from 'react-native-device-info';
|
||||||
|
|
||||||
export const headers = { 'User-Agent': `RC-RN Mobile/${ DeviceInfo.getVersion() } (build: ${ DeviceInfo.getBuildNumber() }; os: ${ Platform.OS } ${ DeviceInfo.getSystemVersion() })` };
|
// this form is required by Rocket.Chat's parser in "app/statistics/server/lib/UAParserCustom.js"
|
||||||
|
export const headers = { 'User-Agent': `RC Mobile; ${ Platform.OS } ${ DeviceInfo.getSystemVersion() }; v${ DeviceInfo.getVersion() } (${ DeviceInfo.getBuildNumber() })` };
|
||||||
|
|
||||||
export default (url, options = {}) => {
|
export default (url, options = {}) => {
|
||||||
let customOptions = { ...options, headers };
|
let customOptions = { ...options, headers };
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
/* eslint-disable quote-props */
|
||||||
|
/* eslint-disable object-curly-newline */
|
||||||
|
/* eslint-disable object-curly-spacing */
|
||||||
|
/* eslint-disable comma-spacing */
|
||||||
|
/* eslint-disable key-spacing */
|
||||||
|
const ascii = {'*\\0/*':'🙆','*\\O/*':'🙆','-___-':'😑',':\'-)':'😂','\':-)':'😅','\':-D':'😅','>:-)':'😆','\':-(':'😓','>:-(':'😠',':\'-(':'😢','O:-)':'😇','0:-3':'😇','0:-)':'😇','0;^)':'😇','O;-)':'😇','0;-)':'😇','O:-3':'😇','-__-':'😑',':-Þ':'😛','</3':'💔',':\')':'😂',':-D':'😃','\':)':'😅','\'=)':'😅','\':D':'😅','\'=D':'😅','>:)':'😆','>;)':'😆','>=)':'😆',';-)':'😉','*-)':'😉',';-]':'😉',';^)':'😉','\':(':'😓','\'=(':'😓',':-*':'😘',':^*':'😘','>:P':'😜','X-P':'😜','>:[':'😞',':-(':'😞',':-[':'😞','>:(':'😠',':\'(':'😢',';-(':'😢','>.<':'😣','#-)':'😵','%-)':'😵','X-)':'😵','\\0/':'🙆','\\O/':'🙆','0:3':'😇','0:)':'😇','O:)':'😇','O=)':'😇','O:3':'😇','B-)':'😎','8-)':'😎','B-D':'😎','8-D':'😎','-_-':'😑','>:\\':'😕','>:/':'😕',':-/':'😕',':-.':'😕',':-P':'😛',':Þ':'😛',':-b':'😛',':-O':'😮','O_O':'😮','>:O':'😮',':-X':'😶',':-#':'😶',':-)':'🙂','(y)':'👍','<3':'❤','=D':'😃',';)':'😉','*)':'😉',';]':'😉',';D':'😉',':*':'😘','=*':'😘',':(':'😞',':[':'😞','=(':'😞',':@':'😠',';(':'😢','D:':'😨',':$':'😳','=$':'😳','#)':'😵','%)':'😵','X)':'😵','B)':'😎','8)':'😎',':/':'😕',':\\':'😕','=/':'😕','=\\':'😕',':L':'😕','=L':'😕',':P':'😛','=P':'😛',':b':'😛',':O':'😮',':X':'😶',':#':'😶','=X':'😶','=#':'😶',':)':'🙂','=]':'🙂','=)':'🙂',':]':'🙂',':D':'😄'};
|
||||||
|
|
||||||
|
export const asciiRegexp = '(\\*\\\\0\\/\\*|\\*\\\\O\\/\\*|\\-___\\-|\\:\'\\-\\)|\'\\:\\-\\)|\'\\:\\-D|\\>\\:\\-\\)|>\\:\\-\\)|\'\\:\\-\\(|\\>\\:\\-\\(|>\\:\\-\\(|\\:\'\\-\\(|O\\:\\-\\)|0\\:\\-3|0\\:\\-\\)|0;\\^\\)|O;\\-\\)|0;\\-\\)|O\\:\\-3|\\-__\\-|\\:\\-Þ|\\:\\-Þ|\\<\\/3|<\\/3|\\:\'\\)|\\:\\-D|\'\\:\\)|\'\\=\\)|\'\\:D|\'\\=D|\\>\\:\\)|>\\:\\)|\\>;\\)|>;\\)|\\>\\=\\)|>\\=\\)|;\\-\\)|\\*\\-\\)|;\\-\\]|;\\^\\)|\'\\:\\(|\'\\=\\(|\\:\\-\\*|\\:\\^\\*|\\>\\:P|>\\:P|X\\-P|\\>\\:\\[|>\\:\\[|\\:\\-\\(|\\:\\-\\[|\\>\\:\\(|>\\:\\(|\\:\'\\(|;\\-\\(|\\>\\.\\<|>\\.<|#\\-\\)|%\\-\\)|X\\-\\)|\\\\0\\/|\\\\O\\/|0\\:3|0\\:\\)|O\\:\\)|O\\=\\)|O\\:3|B\\-\\)|8\\-\\)|B\\-D|8\\-D|\\-_\\-|\\>\\:\\\\|>\\:\\\\|\\>\\:\\/|>\\:\\/|\\:\\-\\/|\\:\\-\\.|\\:\\-P|\\:Þ|\\:Þ|\\:\\-b|\\:\\-O|O_O|\\>\\:O|>\\:O|\\:\\-X|\\:\\-#|\\:\\-\\)|\\(y\\)|\\<3|<3|\\=D|;\\)|\\*\\)|;\\]|;D|\\:\\*|\\=\\*|\\:\\(|\\:\\[|\\=\\(|\\:@|;\\(|D\\:|\\:\\$|\\=\\$|#\\)|%\\)|X\\)|B\\)|8\\)|\\:\\/|\\:\\\\|\\=\\/|\\=\\\\|\\:L|\\=L|\\:P|\\=P|\\:b|\\:O|\\:X|\\:#|\\=X|\\=#|\\:\\)|\\=\\]|\\=\\)|\\:\\]|\\:D)';
|
||||||
|
|
||||||
|
export default ascii;
|
|
@ -0,0 +1,46 @@
|
||||||
|
import emojis from './emojis';
|
||||||
|
import ascii, { asciiRegexp } from './ascii';
|
||||||
|
|
||||||
|
const shortnamePattern = new RegExp(/:[-+_a-z0-9]+:/, 'gi');
|
||||||
|
const replaceShortNameWithUnicode = shortname => emojis[shortname] || shortname;
|
||||||
|
const regAscii = new RegExp(`((\\s|^)${ asciiRegexp }(?=\\s|$|[!,.?]))`, 'gi');
|
||||||
|
|
||||||
|
const unescapeHTML = (string) => {
|
||||||
|
const unescaped = {
|
||||||
|
'&': '&',
|
||||||
|
'&': '&',
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'<': '<',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'>': '>',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
'"': '"',
|
||||||
|
'"': '"',
|
||||||
|
''': '\'',
|
||||||
|
''': '\'',
|
||||||
|
''': '\''
|
||||||
|
};
|
||||||
|
|
||||||
|
return string.replace(/&(?:amp|#38|#x26|lt|#60|#x3C|gt|#62|#x3E|apos|#39|#x27|quot|#34|#x22);/ig, match => unescaped[match]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const shortnameToUnicode = (str) => {
|
||||||
|
str = str.replace(shortnamePattern, replaceShortNameWithUnicode);
|
||||||
|
|
||||||
|
str = str.replace(regAscii, (entire, m1, m2, m3) => {
|
||||||
|
if (!m3 || (!(unescapeHTML(m3) in ascii))) {
|
||||||
|
// if the ascii doesnt exist just return the entire match
|
||||||
|
return entire;
|
||||||
|
}
|
||||||
|
|
||||||
|
m3 = unescapeHTML(m3);
|
||||||
|
return ascii[m3];
|
||||||
|
});
|
||||||
|
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default shortnameToUnicode;
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* eslint-disable no-undef */
|
||||||
|
import shortnameToUnicode from './index';
|
||||||
|
|
||||||
|
test('render joy', () => {
|
||||||
|
expect(shortnameToUnicode(':joy:')).toBe('😂');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render several emojis', () => {
|
||||||
|
expect(shortnameToUnicode(':dog::cat::hamburger::icecream::rocket:')).toBe('🐶🐱🍔🍦🚀');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render unknown emoji', () => {
|
||||||
|
expect(shortnameToUnicode(':unknown:')).toBe(':unknown:');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render empty', () => {
|
||||||
|
expect(shortnameToUnicode('')).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render text with emoji', () => {
|
||||||
|
expect(shortnameToUnicode('Hello there! :hugging:')).toBe('Hello there! 🤗');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render ascii smile', () => {
|
||||||
|
expect(shortnameToUnicode(':)')).toBe('🙂');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render several ascii emojis', () => {
|
||||||
|
expect(shortnameToUnicode(':) :( -_- \':-D')).toBe('🙂😞😑😅');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render text with ascii emoji', () => {
|
||||||
|
expect(shortnameToUnicode('Hello there! :)')).toBe('Hello there!🙂');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('render emoji and ascii emoji', () => {
|
||||||
|
expect(shortnameToUnicode('\':-D :joy:')).toBe('😅 😂');
|
||||||
|
});
|
|
@ -0,0 +1,152 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { StyleSheet, View, PermissionsAndroid } from 'react-native';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import CameraRoll from '@react-native-community/cameraroll';
|
||||||
|
import * as mime from 'react-native-mime-types';
|
||||||
|
import { FileSystem } from 'react-native-unimodules';
|
||||||
|
import { Video } from 'expo-av';
|
||||||
|
import SHA256 from 'js-sha256';
|
||||||
|
|
||||||
|
import { LISTENER } from '../containers/Toast';
|
||||||
|
import EventEmitter from '../utils/events';
|
||||||
|
import I18n from '../i18n';
|
||||||
|
import { withTheme } from '../theme';
|
||||||
|
import ImageViewer from '../presentation/ImageViewer';
|
||||||
|
import { themedHeader } from '../utils/navigation';
|
||||||
|
import { themes } from '../constants/colors';
|
||||||
|
import { formatAttachmentUrl } from '../lib/utils';
|
||||||
|
import RCActivityIndicator from '../containers/ActivityIndicator';
|
||||||
|
import { SaveButton, CloseModalButton } from '../containers/HeaderButton';
|
||||||
|
import { isAndroid } from '../utils/deviceInfo';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class AttachmentView extends React.Component {
|
||||||
|
static navigationOptions = ({ navigation, screenProps }) => {
|
||||||
|
const { theme } = screenProps;
|
||||||
|
const attachment = navigation.getParam('attachment');
|
||||||
|
const from = navigation.getParam('from');
|
||||||
|
const handleSave = navigation.getParam('handleSave', () => {});
|
||||||
|
const { title, video_url } = attachment;
|
||||||
|
const options = {
|
||||||
|
title,
|
||||||
|
...themedHeader(theme),
|
||||||
|
headerRight: !video_url ? <SaveButton testID='save-image' onPress={handleSave} /> : null
|
||||||
|
};
|
||||||
|
if (from !== 'MessagesView') {
|
||||||
|
options.gesturesEnabled = false;
|
||||||
|
options.headerLeft = <CloseModalButton testID='close-attachment-view' navigation={navigation} />;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object,
|
||||||
|
theme: PropTypes.string,
|
||||||
|
baseUrl: PropTypes.string,
|
||||||
|
user: PropTypes.shape({
|
||||||
|
id: PropTypes.string,
|
||||||
|
token: PropTypes.string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const attachment = props.navigation.getParam('attachment');
|
||||||
|
this.state = { attachment, loading: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
navigation.setParams({ handleSave: this.handleSave });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSave = async() => {
|
||||||
|
const { attachment } = this.state;
|
||||||
|
const { user, baseUrl } = this.props;
|
||||||
|
const { image_url, image_type } = attachment;
|
||||||
|
const img = formatAttachmentUrl(image_url, user.id, user.token, baseUrl);
|
||||||
|
|
||||||
|
if (isAndroid) {
|
||||||
|
const rationale = {
|
||||||
|
title: I18n.t('Write_External_Permission'),
|
||||||
|
message: I18n.t('Write_External_Permission_Message')
|
||||||
|
};
|
||||||
|
const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, rationale);
|
||||||
|
if (!(result || result === PermissionsAndroid.RESULTS.GRANTED)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ loading: true });
|
||||||
|
try {
|
||||||
|
const extension = `.${ mime.extension(image_type) || 'jpg' }`;
|
||||||
|
const file = `${ FileSystem.documentDirectory + SHA256(image_url) + extension }`;
|
||||||
|
const { uri } = await FileSystem.downloadAsync(img, file);
|
||||||
|
await CameraRoll.save(uri, { album: 'Rocket.Chat' });
|
||||||
|
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
|
||||||
|
} catch (e) {
|
||||||
|
EventEmitter.emit(LISTENER, { message: I18n.t('error-save-image') });
|
||||||
|
}
|
||||||
|
this.setState({ loading: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
renderImage = uri => (
|
||||||
|
<ImageViewer
|
||||||
|
uri={uri}
|
||||||
|
onLoadEnd={() => this.setState({ loading: false })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
renderVideo = uri => (
|
||||||
|
<Video
|
||||||
|
source={{ uri }}
|
||||||
|
rate={1.0}
|
||||||
|
volume={1.0}
|
||||||
|
isMuted={false}
|
||||||
|
resizeMode={Video.RESIZE_MODE_CONTAIN}
|
||||||
|
shouldPlay
|
||||||
|
isLooping={false}
|
||||||
|
style={styles.container}
|
||||||
|
useNativeControls
|
||||||
|
onLoad={() => this.setState({ loading: false })}
|
||||||
|
onError={console.log}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, attachment } = this.state;
|
||||||
|
const { theme, user, baseUrl } = this.props;
|
||||||
|
let content = null;
|
||||||
|
|
||||||
|
if (attachment && attachment.image_url) {
|
||||||
|
const uri = formatAttachmentUrl(attachment.image_url, user.id, user.token, baseUrl);
|
||||||
|
content = this.renderImage(encodeURI(uri));
|
||||||
|
} else if (attachment && attachment.video_url) {
|
||||||
|
const uri = formatAttachmentUrl(attachment.video_url, user.id, user.token, baseUrl);
|
||||||
|
content = this.renderVideo(encodeURI(uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
|
{content}
|
||||||
|
{loading ? <RCActivityIndicator absolute size='large' theme={theme} /> : null}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
|
user: {
|
||||||
|
id: state.login.user && state.login.user.id,
|
||||||
|
token: state.login.user && state.login.user.token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(withTheme(AttachmentView));
|
|
@ -1,30 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, Image } from 'react-native';
|
|
||||||
|
|
||||||
import StatusBar from '../containers/StatusBar';
|
import StatusBar from '../containers/StatusBar';
|
||||||
import { isAndroid } from '../utils/deviceInfo';
|
|
||||||
import { withTheme } from '../theme';
|
import { withTheme } from '../theme';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
image: {
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
backgroundColor: 'white'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default React.memo(withTheme(({ theme }) => (
|
export default React.memo(withTheme(({ theme }) => (
|
||||||
<>
|
<>
|
||||||
<StatusBar theme={theme} />
|
<StatusBar theme={theme} />
|
||||||
{isAndroid
|
|
||||||
? (
|
|
||||||
<Image
|
|
||||||
source={{ uri: 'launch_screen' }}
|
|
||||||
style={styles.image}
|
|
||||||
resizeMode='contain'
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</>
|
</>
|
||||||
)));
|
)));
|
||||||
|
|
|
@ -2,6 +2,8 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { WebView } from 'react-native-webview';
|
import { WebView } from 'react-native-webview';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import parse from 'url-parse';
|
||||||
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import { isIOS } from '../utils/deviceInfo';
|
import { isIOS } from '../utils/deviceInfo';
|
||||||
import { CloseModalButton } from '../containers/HeaderButton';
|
import { CloseModalButton } from '../containers/HeaderButton';
|
||||||
|
@ -9,6 +11,7 @@ import StatusBar from '../containers/StatusBar';
|
||||||
import ActivityIndicator from '../containers/ActivityIndicator';
|
import ActivityIndicator from '../containers/ActivityIndicator';
|
||||||
import { withTheme } from '../theme';
|
import { withTheme } from '../theme';
|
||||||
import { themedHeader } from '../utils/navigation';
|
import { themedHeader } from '../utils/navigation';
|
||||||
|
import debounce from '../utils/debounce';
|
||||||
|
|
||||||
const userAgent = isIOS
|
const userAgent = isIOS
|
||||||
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'
|
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'
|
||||||
|
@ -40,6 +43,12 @@ class AuthenticationWebView extends React.PureComponent {
|
||||||
this.redirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
|
this.redirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.debouncedLogin && this.debouncedLogin.stop) {
|
||||||
|
this.debouncedLogin.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dismiss = () => {
|
dismiss = () => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
navigation.pop();
|
navigation.pop();
|
||||||
|
@ -62,24 +71,25 @@ class AuthenticationWebView extends React.PureComponent {
|
||||||
this.dismiss();
|
this.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
|
debouncedLogin = debounce(params => this.login(params), 3000);
|
||||||
|
|
||||||
onNavigationStateChange = (webViewState) => {
|
onNavigationStateChange = (webViewState) => {
|
||||||
const url = decodeURIComponent(webViewState.url);
|
const url = decodeURIComponent(webViewState.url);
|
||||||
if (this.authType === 'saml' || this.authType === 'cas') {
|
if (this.authType === 'saml' || this.authType === 'cas') {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
const ssoToken = navigation.getParam('ssoToken');
|
const ssoToken = navigation.getParam('ssoToken');
|
||||||
if (url.includes('ticket') || url.includes('validate')) {
|
if (url.includes('ticket') || url.includes('validate') || url.includes('saml_idp_credentialToken')) {
|
||||||
let payload;
|
let payload;
|
||||||
const credentialToken = { credentialToken: ssoToken };
|
|
||||||
if (this.authType === 'saml') {
|
if (this.authType === 'saml') {
|
||||||
|
const parsedUrl = parse(url, true);
|
||||||
|
const token = (parsedUrl.query && parsedUrl.query.saml_idp_credentialToken) || ssoToken;
|
||||||
|
const credentialToken = { credentialToken: token };
|
||||||
payload = { ...credentialToken, saml: true };
|
payload = { ...credentialToken, saml: true };
|
||||||
} else {
|
} else {
|
||||||
payload = { cas: credentialToken };
|
payload = { cas: { credentialToken: ssoToken } };
|
||||||
}
|
}
|
||||||
// We need to set a timeout when the login is done with SSO in order to make it work on our side.
|
this.debouncedLogin(payload);
|
||||||
// It is actually due to the SSO server processing the response.
|
|
||||||
setTimeout(() => {
|
|
||||||
this.login(payload);
|
|
||||||
}, 3000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { ScrollView, View } from 'react-native';
|
||||||
|
import { SafeAreaView } from 'react-navigation';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import RNPickerSelect from 'react-native-picker-select';
|
||||||
|
|
||||||
|
import {
|
||||||
|
inviteLinksSetParams as inviteLinksSetParamsAction,
|
||||||
|
inviteLinksCreate as inviteLinksCreateAction
|
||||||
|
} from '../../actions/inviteLinks';
|
||||||
|
import ListItem from '../../containers/ListItem';
|
||||||
|
import styles from './styles';
|
||||||
|
import Button from '../../containers/Button';
|
||||||
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import StatusBar from '../../containers/StatusBar';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
import { withTheme } from '../../theme';
|
||||||
|
import { themedHeader } from '../../utils/navigation';
|
||||||
|
import Separator from '../../containers/Separator';
|
||||||
|
|
||||||
|
const OPTIONS = {
|
||||||
|
days: [{
|
||||||
|
label: I18n.t('Never'), value: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1', value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '7', value: 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '15', value: 15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '30', value: 30
|
||||||
|
}],
|
||||||
|
maxUses: [{
|
||||||
|
label: I18n.t('No_limit'), value: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1', value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '5', value: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '10', value: 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '25', value: 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '50', value: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '100', value: 100
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
class InviteUsersView extends React.Component {
|
||||||
|
static navigationOptions = ({ screenProps }) => ({
|
||||||
|
title: I18n.t('Invite_users'),
|
||||||
|
...themedHeader(screenProps.theme)
|
||||||
|
})
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object,
|
||||||
|
theme: PropTypes.string,
|
||||||
|
timeDateFormat: PropTypes.string,
|
||||||
|
createInviteLink: PropTypes.func,
|
||||||
|
inviteLinksSetParams: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.rid = props.navigation.getParam('rid');
|
||||||
|
}
|
||||||
|
|
||||||
|
onValueChangePicker = (key, value) => {
|
||||||
|
const { inviteLinksSetParams } = this.props;
|
||||||
|
const params = {
|
||||||
|
[key]: value
|
||||||
|
};
|
||||||
|
inviteLinksSetParams(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
createInviteLink = () => {
|
||||||
|
const { createInviteLink, navigation } = this.props;
|
||||||
|
createInviteLink(this.rid);
|
||||||
|
navigation.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPicker = (key) => {
|
||||||
|
const { props } = this;
|
||||||
|
const { theme } = props;
|
||||||
|
return (
|
||||||
|
<RNPickerSelect
|
||||||
|
style={{ viewContainer: styles.viewContainer }}
|
||||||
|
value={props[key]}
|
||||||
|
textInputProps={{ style: { ...styles.pickerText, color: themes[theme].actionTintColor } }}
|
||||||
|
useNativeAndroidPickerStyle={false}
|
||||||
|
placeholder={{}}
|
||||||
|
onValueChange={value => this.onValueChangePicker(key, value)}
|
||||||
|
items={OPTIONS[key]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { theme } = this.props;
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} forceInset={{ vertical: 'never' }}>
|
||||||
|
<ScrollView
|
||||||
|
{...scrollPersistTaps}
|
||||||
|
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||||
|
contentContainerStyle={styles.contentContainer}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
<StatusBar theme={theme} />
|
||||||
|
<Separator theme={theme} />
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Expiration_Days')}
|
||||||
|
right={() => this.renderPicker('days')}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<Separator theme={theme} />
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Max_number_of_uses')}
|
||||||
|
right={() => this.renderPicker('maxUses')}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<Separator theme={theme} />
|
||||||
|
<View style={styles.innerContainer}>
|
||||||
|
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
|
||||||
|
<Button
|
||||||
|
title={I18n.t('Generate_New_Link')}
|
||||||
|
type='primary'
|
||||||
|
onPress={this.createInviteLink}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
days: state.inviteLinks.days,
|
||||||
|
maxUses: state.inviteLinks.maxUses
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
inviteLinksSetParams: params => dispatch(inviteLinksSetParamsAction(params)),
|
||||||
|
createInviteLink: rid => dispatch(inviteLinksCreateAction(rid))
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(InviteUsersView));
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
import sharedStyles from '../Styles';
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
innerContainer: {
|
||||||
|
paddingHorizontal: 20
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
width: '100%',
|
||||||
|
height: StyleSheet.hairlineWidth,
|
||||||
|
marginVertical: 20
|
||||||
|
},
|
||||||
|
sectionSeparatorBorder: {
|
||||||
|
height: 10
|
||||||
|
},
|
||||||
|
marginBottom: {
|
||||||
|
height: 30
|
||||||
|
},
|
||||||
|
contentContainer: {
|
||||||
|
marginVertical: 10
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
fontSize: 13,
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
paddingVertical: 10
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
...sharedStyles.separatorBottom,
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
paddingVertical: 10,
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
viewContainer: {
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
pickerText: {
|
||||||
|
...sharedStyles.textRegular,
|
||||||
|
fontSize: 16
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,151 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { View, Share, ScrollView } from 'react-native';
|
||||||
|
import { SafeAreaView } from 'react-navigation';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
inviteLinksCreate as inviteLinksCreateAction,
|
||||||
|
inviteLinksClear as inviteLinksClearAction
|
||||||
|
} from '../../actions/inviteLinks';
|
||||||
|
import RCTextInput from '../../containers/TextInput';
|
||||||
|
import styles from './styles';
|
||||||
|
import Markdown from '../../containers/markdown';
|
||||||
|
import Button from '../../containers/Button';
|
||||||
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import StatusBar from '../../containers/StatusBar';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
import { withTheme } from '../../theme';
|
||||||
|
import { themedHeader } from '../../utils/navigation';
|
||||||
|
|
||||||
|
class InviteUsersView extends React.Component {
|
||||||
|
static navigationOptions = ({ screenProps }) => ({
|
||||||
|
title: I18n.t('Invite_users'),
|
||||||
|
...themedHeader(screenProps.theme)
|
||||||
|
})
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object,
|
||||||
|
theme: PropTypes.string,
|
||||||
|
timeDateFormat: PropTypes.string,
|
||||||
|
invite: PropTypes.object,
|
||||||
|
createInviteLink: PropTypes.func,
|
||||||
|
clearInviteLink: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.rid = props.navigation.getParam('rid');
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const { createInviteLink } = this.props;
|
||||||
|
createInviteLink(this.rid);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const { clearInviteLink } = this.props;
|
||||||
|
clearInviteLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
share = () => {
|
||||||
|
const { invite } = this.props;
|
||||||
|
if (!invite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Share.share({ message: invite.url });
|
||||||
|
}
|
||||||
|
|
||||||
|
edit = () => {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
navigation.navigate('InviteUsersEditView', { rid: this.rid });
|
||||||
|
}
|
||||||
|
|
||||||
|
linkExpirationText = () => {
|
||||||
|
const { timeDateFormat, invite } = this.props;
|
||||||
|
|
||||||
|
if (!invite || !invite.url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invite.expires) {
|
||||||
|
const expiration = new Date(invite.expires);
|
||||||
|
|
||||||
|
if (invite.maxUses) {
|
||||||
|
const usesLeft = invite.maxUses - invite.uses;
|
||||||
|
return I18n.t('Your_invite_link_will_expire_on__date__or_after__usesLeft__uses', { date: moment(expiration).format(timeDateFormat), usesLeft });
|
||||||
|
}
|
||||||
|
|
||||||
|
return I18n.t('Your_invite_link_will_expire_on__date__', { date: moment(expiration).format(timeDateFormat) });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invite.maxUses) {
|
||||||
|
const usesLeft = invite.maxUses - invite.uses;
|
||||||
|
return I18n.t('Your_invite_link_will_expire_after__usesLeft__uses', { usesLeft });
|
||||||
|
}
|
||||||
|
|
||||||
|
return I18n.t('Your_invite_link_will_never_expire');
|
||||||
|
}
|
||||||
|
|
||||||
|
renderExpiration = () => {
|
||||||
|
const { theme } = this.props;
|
||||||
|
const expirationMessage = this.linkExpirationText();
|
||||||
|
return <Markdown msg={expirationMessage} username='' baseUrl='' theme={theme} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
theme, invite
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} forceInset={{ vertical: 'never' }}>
|
||||||
|
<ScrollView
|
||||||
|
{...scrollPersistTaps}
|
||||||
|
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||||
|
contentContainerStyle={styles.contentContainer}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
<StatusBar theme={theme} />
|
||||||
|
<View style={styles.innerContainer}>
|
||||||
|
<RCTextInput
|
||||||
|
label={I18n.t('Invite_Link')}
|
||||||
|
theme={theme}
|
||||||
|
value={invite && invite.url}
|
||||||
|
editable={false}
|
||||||
|
/>
|
||||||
|
{this.renderExpiration()}
|
||||||
|
<View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} />
|
||||||
|
<Button
|
||||||
|
title={I18n.t('Share_Link')}
|
||||||
|
type='primary'
|
||||||
|
onPress={this.share}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
title={I18n.t('Edit_Invite')}
|
||||||
|
type='secondary'
|
||||||
|
onPress={this.edit}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
timeDateFormat: state.settings.Message_TimeAndDateFormat,
|
||||||
|
days: state.inviteLinks.days,
|
||||||
|
maxUses: state.inviteLinks.maxUses,
|
||||||
|
invite: state.inviteLinks.invite
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
createInviteLink: rid => dispatch(inviteLinksCreateAction(rid)),
|
||||||
|
clearInviteLink: () => dispatch(inviteLinksClearAction())
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(InviteUsersView));
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
export default StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
innerContainer: {
|
||||||
|
padding: 20,
|
||||||
|
paddingBottom: 0
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
width: '100%',
|
||||||
|
height: StyleSheet.hairlineWidth,
|
||||||
|
marginVertical: 20
|
||||||
|
}
|
||||||
|
});
|
|
@ -27,7 +27,8 @@ const styles = StyleSheet.create({
|
||||||
paddingVertical: 30
|
paddingVertical: 30
|
||||||
},
|
},
|
||||||
safeArea: {
|
safeArea: {
|
||||||
paddingBottom: 30
|
paddingBottom: 30,
|
||||||
|
flex: 1
|
||||||
},
|
},
|
||||||
serviceButton: {
|
serviceButton: {
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
|
@ -231,6 +232,17 @@ class LoginSignupView extends React.Component {
|
||||||
this.openOAuth({ url });
|
this.openOAuth({ url });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPressWordpress = () => {
|
||||||
|
const { services, server } = this.props;
|
||||||
|
const { clientId, serverURL } = services.wordpress;
|
||||||
|
const endpoint = `${ serverURL }/oauth/authorize`;
|
||||||
|
const redirect_uri = `${ server }/_oauth/wordpress?close`;
|
||||||
|
const scope = 'openid';
|
||||||
|
const state = this.getOAuthState();
|
||||||
|
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
|
||||||
|
this.openOAuth({ url: `${ endpoint }${ params }` });
|
||||||
|
}
|
||||||
|
|
||||||
onPressCustomOAuth = (loginService) => {
|
onPressCustomOAuth = (loginService) => {
|
||||||
const { server } = this.props;
|
const { server } = this.props;
|
||||||
const {
|
const {
|
||||||
|
@ -313,7 +325,8 @@ class LoginSignupView extends React.Component {
|
||||||
google: this.onPressGoogle,
|
google: this.onPressGoogle,
|
||||||
linkedin: this.onPressLinkedin,
|
linkedin: this.onPressLinkedin,
|
||||||
'meteor-developer': this.onPressMeteor,
|
'meteor-developer': this.onPressMeteor,
|
||||||
twitter: this.onPressTwitter
|
twitter: this.onPressTwitter,
|
||||||
|
wordpress: this.onPressWordpress
|
||||||
};
|
};
|
||||||
return oauthProviders[name];
|
return oauthProviders[name];
|
||||||
}
|
}
|
||||||
|
@ -421,6 +434,11 @@ class LoginSignupView extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
return (
|
return (
|
||||||
|
<SafeAreaView
|
||||||
|
testID='welcome-view'
|
||||||
|
forceInset={{ vertical: 'never' }}
|
||||||
|
style={[styles.safeArea, { backgroundColor: themes[theme].backgroundColor }]}
|
||||||
|
>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={[
|
style={[
|
||||||
sharedStyles.containerScrollView,
|
sharedStyles.containerScrollView,
|
||||||
|
@ -432,7 +450,6 @@ class LoginSignupView extends React.Component {
|
||||||
{...scrollPersistTaps}
|
{...scrollPersistTaps}
|
||||||
>
|
>
|
||||||
<StatusBar theme={theme} />
|
<StatusBar theme={theme} />
|
||||||
<SafeAreaView testID='welcome-view' forceInset={{ vertical: 'never' }} style={styles.safeArea}>
|
|
||||||
{this.renderServices()}
|
{this.renderServices()}
|
||||||
{this.renderServicesSeparator()}
|
{this.renderServicesSeparator()}
|
||||||
<Button
|
<Button
|
||||||
|
@ -449,8 +466,8 @@ class LoginSignupView extends React.Component {
|
||||||
theme={theme}
|
theme={theme}
|
||||||
testID='welcome-view-register'
|
testID='welcome-view-register'
|
||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,10 +70,6 @@ class LoginView extends React.Component {
|
||||||
Accounts_PasswordReset: true
|
Accounts_PasswordReset: true
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
Accounts_PasswordReset: true
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
|
|
@ -13,9 +13,9 @@ import I18n from '../../i18n';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import getFileUrlFromMessage from '../../lib/methods/helpers/getFileUrlFromMessage';
|
import getFileUrlFromMessage from '../../lib/methods/helpers/getFileUrlFromMessage';
|
||||||
import FileModal from '../../containers/FileModal';
|
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
|
import { withSplit } from '../../split';
|
||||||
import { themedHeader } from '../../utils/navigation';
|
import { themedHeader } from '../../utils/navigation';
|
||||||
|
|
||||||
const ACTION_INDEX = 0;
|
const ACTION_INDEX = 0;
|
||||||
|
@ -32,7 +32,8 @@ class MessagesView extends React.Component {
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
navigation: PropTypes.object,
|
navigation: PropTypes.object,
|
||||||
customEmojis: PropTypes.object,
|
customEmojis: PropTypes.object,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string,
|
||||||
|
split: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -40,8 +41,6 @@ class MessagesView extends React.Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: false,
|
loading: false,
|
||||||
messages: [],
|
messages: [],
|
||||||
selectedAttachment: {},
|
|
||||||
photoModalVisible: false,
|
|
||||||
fileLoading: true
|
fileLoading: true
|
||||||
};
|
};
|
||||||
this.rid = props.navigation.getParam('rid');
|
this.rid = props.navigation.getParam('rid');
|
||||||
|
@ -55,7 +54,7 @@ class MessagesView extends React.Component {
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const {
|
const {
|
||||||
loading, messages, photoModalVisible, fileLoading
|
loading, messages, fileLoading
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
|
@ -64,9 +63,6 @@ class MessagesView extends React.Component {
|
||||||
if (nextState.loading !== loading) {
|
if (nextState.loading !== loading) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (nextState.photoModalVisible !== photoModalVisible) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.messages, messages)) {
|
if (!equal(nextState.messages, messages)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -90,7 +86,7 @@ class MessagesView extends React.Component {
|
||||||
isEdited: !!item.editedAt,
|
isEdited: !!item.editedAt,
|
||||||
isHeader: true,
|
isHeader: true,
|
||||||
attachments: item.attachments || [],
|
attachments: item.attachments || [],
|
||||||
onOpenFileModal: this.onOpenFileModal,
|
showAttachment: this.showAttachment,
|
||||||
getCustomEmoji: this.getCustomEmoji
|
getCustomEmoji: this.getCustomEmoji
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -215,12 +211,13 @@ class MessagesView extends React.Component {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenFileModal = (attachment) => {
|
showAttachment = (attachment) => {
|
||||||
this.setState({ selectedAttachment: attachment, photoModalVisible: true });
|
const { navigation, split } = this.props;
|
||||||
|
let params = { attachment };
|
||||||
|
if (split) {
|
||||||
|
params = { ...params, from: 'MessagesView' };
|
||||||
}
|
}
|
||||||
|
navigation.navigate('AttachmentView', params);
|
||||||
onCloseFileModal = () => {
|
|
||||||
this.setState({ selectedAttachment: {}, photoModalVisible: false });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onLongPress = (message) => {
|
onLongPress = (message) => {
|
||||||
|
@ -278,10 +275,8 @@ class MessagesView extends React.Component {
|
||||||
renderItem = ({ item }) => this.content.renderItem(item)
|
renderItem = ({ item }) => this.content.renderItem(item)
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { messages, loading } = this.state;
|
||||||
messages, loading, selectedAttachment, photoModalVisible, fileLoading
|
const { theme } = this.props;
|
||||||
} = this.state;
|
|
||||||
const { user, baseUrl, theme } = this.props;
|
|
||||||
|
|
||||||
if (!loading && messages.length === 0) {
|
if (!loading && messages.length === 0) {
|
||||||
return this.renderEmpty();
|
return this.renderEmpty();
|
||||||
|
@ -305,15 +300,6 @@ class MessagesView extends React.Component {
|
||||||
onEndReached={this.load}
|
onEndReached={this.load}
|
||||||
ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null}
|
ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null}
|
||||||
/>
|
/>
|
||||||
<FileModal
|
|
||||||
attachment={selectedAttachment}
|
|
||||||
isVisible={photoModalVisible}
|
|
||||||
onClose={this.onCloseFileModal}
|
|
||||||
user={user}
|
|
||||||
baseUrl={baseUrl}
|
|
||||||
loading={fileLoading}
|
|
||||||
setLoading={this.setFileLoading}
|
|
||||||
/>
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -329,4 +315,4 @@ const mapStateToProps = state => ({
|
||||||
customEmojis: state.customEmojis
|
customEmojis: state.customEmojis
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withTheme(MessagesView));
|
export default connect(mapStateToProps)(withSplit(withTheme(MessagesView)));
|
||||||
|
|