forked from Ivasoft/mattermost-mobile
Compare commits
24 Commits
test1.0.2
...
release-2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d653c4e19 | ||
|
|
fd25ea163d | ||
|
|
55578a0dce | ||
|
|
a9f325ef43 | ||
|
|
24ee8cc98e | ||
|
|
c27e1116cc | ||
|
|
2aaa366558 | ||
|
|
e72a142974 | ||
|
|
7165830fe0 | ||
|
|
ce5d049a55 | ||
|
|
8d9fab9b53 | ||
|
|
70cf8c5593 | ||
|
|
c9773d031d | ||
|
|
d75b854828 | ||
|
|
f1a06396c6 | ||
|
|
d1cbfe6659 | ||
|
|
ff18feeac4 | ||
|
|
05984b7202 | ||
|
|
511525c9ed | ||
|
|
055c9109ef | ||
|
|
d484a4ff45 | ||
|
|
e6a1cbb2aa | ||
|
|
c77f1dbd6d | ||
|
|
5f349e378e |
@@ -114,6 +114,7 @@ commands:
|
||||
command: |
|
||||
NODE_ENV=development npm ci --ignore-scripts
|
||||
node node_modules/\@sentry/cli/scripts/install.js
|
||||
node node_modules/react-native-webrtc/tools/downloadWebRTC.js
|
||||
- save_cache:
|
||||
name: Save npm cache
|
||||
key: v2-npm-{{ checksum "package.json" }}-{{ arch }}
|
||||
|
||||
@@ -112,8 +112,8 @@ android {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 453
|
||||
versionName "2.0.0"
|
||||
versionCode 457
|
||||
versionName "2.1.0"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<!-- Request legacy Bluetooth permissions on older devices. -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH"
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.mattermost.helpers
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.util.LruCache
|
||||
|
||||
class BitmapCache {
|
||||
private var memoryCache: LruCache<String, Bitmap>
|
||||
|
||||
init {
|
||||
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
|
||||
val cacheSize = maxMemory / 8
|
||||
memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
|
||||
override fun sizeOf(key: String, bitmap: Bitmap): Int {
|
||||
return bitmap.byteCount / 1024
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getBitmapFromMemCache(key: String): Bitmap? {
|
||||
return memoryCache.get(key)
|
||||
}
|
||||
|
||||
fun addBitmapToMemoryCache(key: String, bitmap: Bitmap) {
|
||||
if (getBitmapFromMemCache(key) == null) {
|
||||
memoryCache.put(key, bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
@@ -28,7 +27,6 @@ import androidx.core.app.Person;
|
||||
import androidx.core.app.RemoteInput;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.mattermost.rnbeta.*;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -53,7 +51,11 @@ public class CustomPushNotificationHelper {
|
||||
private static NotificationChannel mHighImportanceChannel;
|
||||
private static NotificationChannel mMinImportanceChannel;
|
||||
|
||||
private static void addMessagingStyleMessages(Context context, NotificationCompat.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
|
||||
private static final OkHttpClient client = new OkHttpClient();
|
||||
|
||||
private static final BitmapCache bitmapCache = new BitmapCache();
|
||||
|
||||
private static void addMessagingStyleMessages(NotificationCompat.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
|
||||
String message = bundle.getString("message", bundle.getString("body"));
|
||||
String senderId = bundle.getString("sender_id");
|
||||
String serverUrl = bundle.getString("server_url");
|
||||
@@ -75,7 +77,7 @@ public class CustomPushNotificationHelper {
|
||||
|
||||
if (serverUrl != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
try {
|
||||
Bitmap avatar = userAvatar(context, serverUrl, senderId, urlOverride);
|
||||
Bitmap avatar = userAvatar(serverUrl, senderId, urlOverride);
|
||||
if (avatar != null) {
|
||||
sender.setIcon(IconCompat.createWithBitmap(avatar));
|
||||
}
|
||||
@@ -177,12 +179,12 @@ public class CustomPushNotificationHelper {
|
||||
String groupId = is_crt_enabled && !android.text.TextUtils.isEmpty(rootId) ? rootId : channelId;
|
||||
|
||||
addNotificationExtras(notification, bundle);
|
||||
setNotificationIcons(context, notification, bundle);
|
||||
setNotificationMessagingStyle(context, notification, bundle);
|
||||
setNotificationIcons(notification, bundle);
|
||||
setNotificationMessagingStyle(notification, bundle);
|
||||
setNotificationGroup(notification, groupId, createSummary);
|
||||
setNotificationBadgeType(notification);
|
||||
|
||||
setNotificationChannel(notification, bundle);
|
||||
setNotificationChannel(context, notification);
|
||||
setNotificationDeleteIntent(context, notification, bundle, notificationId);
|
||||
addNotificationReplyAction(context, notification, bundle, notificationId);
|
||||
|
||||
@@ -254,15 +256,7 @@ public class CustomPushNotificationHelper {
|
||||
return title;
|
||||
}
|
||||
|
||||
private static int getIconResourceId(Context context, String iconName) {
|
||||
final Resources res = context.getResources();
|
||||
String packageName = context.getPackageName();
|
||||
String defType = "mipmap";
|
||||
|
||||
return res.getIdentifier(iconName, defType, packageName);
|
||||
}
|
||||
|
||||
private static NotificationCompat.MessagingStyle getMessagingStyle(Context context, Bundle bundle) {
|
||||
private static NotificationCompat.MessagingStyle getMessagingStyle(Bundle bundle) {
|
||||
NotificationCompat.MessagingStyle messagingStyle;
|
||||
final String senderId = "me";
|
||||
final String serverUrl = bundle.getString("server_url");
|
||||
@@ -275,7 +269,7 @@ public class CustomPushNotificationHelper {
|
||||
|
||||
if (serverUrl != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
|
||||
try {
|
||||
Bitmap avatar = userAvatar(context, serverUrl, "me", urlOverride);
|
||||
Bitmap avatar = userAvatar(serverUrl, "me", urlOverride);
|
||||
if (avatar != null) {
|
||||
sender.setIcon(IconCompat.createWithBitmap(avatar));
|
||||
}
|
||||
@@ -288,7 +282,7 @@ public class CustomPushNotificationHelper {
|
||||
|
||||
String conversationTitle = getConversationTitle(bundle);
|
||||
setMessagingStyleConversationTitle(messagingStyle, conversationTitle, bundle);
|
||||
addMessagingStyleMessages(context, messagingStyle, conversationTitle, bundle);
|
||||
addMessagingStyleMessages(messagingStyle, conversationTitle, bundle);
|
||||
|
||||
return messagingStyle;
|
||||
}
|
||||
@@ -315,25 +309,6 @@ public class CustomPushNotificationHelper {
|
||||
return getConversationTitle(bundle);
|
||||
}
|
||||
|
||||
public static int getSmallIconResourceId(Context context, String iconName) {
|
||||
if (iconName == null) {
|
||||
iconName = "ic_notification";
|
||||
}
|
||||
|
||||
int resourceId = getIconResourceId(context, iconName);
|
||||
|
||||
if (resourceId == 0) {
|
||||
iconName = "ic_launcher";
|
||||
resourceId = getIconResourceId(context, iconName);
|
||||
|
||||
if (resourceId == 0) {
|
||||
resourceId = android.R.drawable.ic_dialog_info;
|
||||
}
|
||||
}
|
||||
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
private static String removeSenderNameFromMessage(String message, String senderName) {
|
||||
int index = message.indexOf(senderName);
|
||||
if (index == 0) {
|
||||
@@ -365,12 +340,15 @@ public class CustomPushNotificationHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static void setNotificationChannel(NotificationCompat.Builder notification, Bundle bundle) {
|
||||
private static void setNotificationChannel(Context context, NotificationCompat.Builder notification) {
|
||||
// If Android Oreo or above we need to register a channel
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mHighImportanceChannel == null) {
|
||||
createNotificationChannels(context);
|
||||
}
|
||||
NotificationChannel notificationChannel = mHighImportanceChannel;
|
||||
notification.setChannelId(notificationChannel.getId());
|
||||
}
|
||||
@@ -386,8 +364,8 @@ public class CustomPushNotificationHelper {
|
||||
notification.setDeleteIntent(deleteIntent);
|
||||
}
|
||||
|
||||
private static void setNotificationMessagingStyle(Context context, NotificationCompat.Builder notification, Bundle bundle) {
|
||||
NotificationCompat.MessagingStyle messagingStyle = getMessagingStyle(context, bundle);
|
||||
private static void setNotificationMessagingStyle(NotificationCompat.Builder notification, Bundle bundle) {
|
||||
NotificationCompat.MessagingStyle messagingStyle = getMessagingStyle(bundle);
|
||||
notification.setStyle(messagingStyle);
|
||||
}
|
||||
|
||||
@@ -400,20 +378,18 @@ public class CustomPushNotificationHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static void setNotificationIcons(Context context, NotificationCompat.Builder notification, Bundle bundle) {
|
||||
String smallIcon = bundle.getString("smallIcon");
|
||||
private static void setNotificationIcons(NotificationCompat.Builder notification, Bundle bundle) {
|
||||
String channelName = getConversationTitle(bundle);
|
||||
String senderName = bundle.getString("sender_name");
|
||||
String serverUrl = bundle.getString("server_url");
|
||||
String urlOverride = bundle.getString("override_icon_url");
|
||||
|
||||
int smallIconResId = getSmallIconResourceId(context, smallIcon);
|
||||
notification.setSmallIcon(smallIconResId);
|
||||
notification.setSmallIcon(R.mipmap.ic_notification);
|
||||
|
||||
if (serverUrl != null && channelName.equals(senderName)) {
|
||||
try {
|
||||
String senderId = bundle.getString("sender_id");
|
||||
Bitmap avatar = userAvatar(context, serverUrl, senderId, urlOverride);
|
||||
Bitmap avatar = userAvatar(serverUrl, senderId, urlOverride);
|
||||
if (avatar != null) {
|
||||
notification.setLargeIcon(avatar);
|
||||
}
|
||||
@@ -423,29 +399,31 @@ public class CustomPushNotificationHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap userAvatar(Context context, final String serverUrl, final String userId, final String urlOverride) throws IOException {
|
||||
private static Bitmap userAvatar(final String serverUrl, final String userId, final String urlOverride) throws IOException {
|
||||
try {
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
Request request;
|
||||
String url;
|
||||
Response response;
|
||||
if (!TextUtils.isEmpty(urlOverride)) {
|
||||
request = new Request.Builder().url(urlOverride).build();
|
||||
Request request = new Request.Builder().url(urlOverride).build();
|
||||
Log.i("ReactNative", String.format("Fetch override profile image %s", urlOverride));
|
||||
response = client.newCall(request).execute();
|
||||
} else {
|
||||
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
|
||||
final String token = Credentials.getCredentialsForServerSync(reactApplicationContext, serverUrl);
|
||||
url = String.format("%s/api/v4/users/%s/image", serverUrl, userId);
|
||||
Bitmap cached = bitmapCache.getBitmapFromMemCache(userId);
|
||||
if (cached != null) {
|
||||
Bitmap bitmap = cached.copy(cached.getConfig(), false);
|
||||
return getCircleBitmap(bitmap);
|
||||
}
|
||||
String url = String.format("api/v4/users/%s/image", userId);
|
||||
Log.i("ReactNative", String.format("Fetch profile image %s", url));
|
||||
request = new Request.Builder()
|
||||
.header("Authorization", String.format("Bearer %s", token))
|
||||
.url(url)
|
||||
.build();
|
||||
response = Network.getSync(serverUrl, url, null);
|
||||
}
|
||||
Response response = client.newCall(request).execute();
|
||||
|
||||
if (response.code() == 200) {
|
||||
assert response.body() != null;
|
||||
byte[] bytes = Objects.requireNonNull(response.body()).bytes();
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||
if (TextUtils.isEmpty(urlOverride) && !TextUtils.isEmpty(userId)) {
|
||||
bitmapCache.addBitmapToMemoryCache(userId, bitmap.copy(bitmap.getConfig(), false));
|
||||
}
|
||||
return getCircleBitmap(bitmap);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,18 @@ class DatabaseHelper {
|
||||
|
||||
val onlyServerUrl: String?
|
||||
get() {
|
||||
val query = "SELECT url FROM Servers WHERE last_active_at != 0 AND identifier != ''"
|
||||
val cursor = defaultDatabase!!.rawQuery(query)
|
||||
if (cursor.count == 1) {
|
||||
cursor.moveToFirst()
|
||||
val url = cursor.getString(0)
|
||||
cursor.close()
|
||||
return url
|
||||
try {
|
||||
val query = "SELECT url FROM Servers WHERE last_active_at != 0 AND identifier != ''"
|
||||
val cursor = defaultDatabase!!.rawQuery(query)
|
||||
if (cursor.count == 1) {
|
||||
cursor.moveToFirst()
|
||||
val url = cursor.getString(0)
|
||||
cursor.close()
|
||||
return url
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// let it fall to return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -38,14 +43,19 @@ class DatabaseHelper {
|
||||
}
|
||||
|
||||
fun getServerUrlForIdentifier(identifier: String): String? {
|
||||
val args: Array<Any?> = arrayOf(identifier)
|
||||
val query = "SELECT url FROM Servers WHERE identifier=?"
|
||||
val cursor = defaultDatabase!!.rawQuery(query, args)
|
||||
if (cursor.count == 1) {
|
||||
cursor.moveToFirst()
|
||||
val url = cursor.getString(0)
|
||||
cursor.close()
|
||||
return url
|
||||
try {
|
||||
val args: Array<Any?> = arrayOf(identifier)
|
||||
val query = "SELECT url FROM Servers WHERE identifier=?"
|
||||
val cursor = defaultDatabase!!.rawQuery(query, args)
|
||||
if (cursor.count == 1) {
|
||||
cursor.moveToFirst()
|
||||
val url = cursor.getString(0)
|
||||
cursor.close()
|
||||
return url
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// let it fall to return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -63,19 +73,25 @@ class DatabaseHelper {
|
||||
return resultMap
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun getDatabaseForServer(context: Context?, serverUrl: String): Database? {
|
||||
val args: Array<Any?> = arrayOf(serverUrl)
|
||||
val query = "SELECT db_path FROM Servers WHERE url=?"
|
||||
val cursor = defaultDatabase!!.rawQuery(query, args)
|
||||
if (cursor.count == 1) {
|
||||
cursor.moveToFirst()
|
||||
val databasePath = cursor.getString(0)
|
||||
cursor.close()
|
||||
return Database(databasePath, context!!)
|
||||
try {
|
||||
val args: Array<Any?> = arrayOf(serverUrl)
|
||||
val query = "SELECT db_path FROM Servers WHERE url=?"
|
||||
val cursor = defaultDatabase!!.rawQuery(query, args)
|
||||
if (cursor.count == 1) {
|
||||
cursor.moveToFirst()
|
||||
val databasePath = cursor.getString(0)
|
||||
cursor.close()
|
||||
return Database(databasePath, context!!)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// let it fall to return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -148,18 +164,23 @@ class DatabaseHelper {
|
||||
}
|
||||
|
||||
fun queryPostSinceForChannel(db: Database?, channelId: String): Double? {
|
||||
if (db != null) {
|
||||
val postsInChannelQuery = "SELECT last_fetched_at FROM MyChannel WHERE id=? LIMIT 1"
|
||||
val cursor1 = db.rawQuery(postsInChannelQuery, arrayOf(channelId))
|
||||
if (cursor1.count == 1) {
|
||||
cursor1.moveToFirst()
|
||||
val lastFetchedAt = cursor1.getDouble(0)
|
||||
cursor1.close()
|
||||
if (lastFetchedAt == 0.0) {
|
||||
return queryLastPostCreateAt(db, channelId)
|
||||
try {
|
||||
if (db != null) {
|
||||
val postsInChannelQuery = "SELECT last_fetched_at FROM MyChannel WHERE id=? LIMIT 1"
|
||||
val cursor1 = db.rawQuery(postsInChannelQuery, arrayOf(channelId))
|
||||
if (cursor1.count == 1) {
|
||||
cursor1.moveToFirst()
|
||||
val lastFetchedAt = cursor1.getDouble(0)
|
||||
cursor1.close()
|
||||
if (lastFetchedAt == 0.0) {
|
||||
return queryLastPostCreateAt(db, channelId)
|
||||
}
|
||||
return lastFetchedAt
|
||||
}
|
||||
return lastFetchedAt
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// let it fall to return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.mattermost.networkclient.APIClientModule;
|
||||
import com.mattermost.networkclient.enums.RetryTypes;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Response;
|
||||
|
||||
|
||||
public class Network {
|
||||
@@ -35,6 +36,16 @@ public class Network {
|
||||
clientModule.post(baseUrl, endpoint, options, promise);
|
||||
}
|
||||
|
||||
public static Response getSync(String baseUrl, String endpoint, ReadableMap options) {
|
||||
createClientIfNeeded(baseUrl);
|
||||
return clientModule.getSync(baseUrl, endpoint, options);
|
||||
}
|
||||
|
||||
public static Response postSync(String baseUrl, String endpoint, ReadableMap options) {
|
||||
createClientIfNeeded(baseUrl);
|
||||
return clientModule.postSync(baseUrl, endpoint, options);
|
||||
}
|
||||
|
||||
private static void createClientOptions() {
|
||||
WritableMap headers = Arguments.createMap();
|
||||
headers.putString("X-Requested-With", "XMLHttpRequest");
|
||||
|
||||
@@ -145,9 +145,9 @@ public class NotificationHelper {
|
||||
for (final StatusBarNotification status : statusNotifications) {
|
||||
Bundle bundle = status.getNotification().extras;
|
||||
if (isThreadNotification) {
|
||||
hasMore = bundle.getString("root_id").equals(rootId);
|
||||
hasMore = bundle.containsKey("root_id") && bundle.getString("root_id").equals(rootId);
|
||||
} else {
|
||||
hasMore = bundle.getString("channel_id").equals(channelId);
|
||||
hasMore = bundle.containsKey("channel_id") && bundle.getString("channel_id").equals(channelId);
|
||||
}
|
||||
if (hasMore) break;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -17,7 +16,6 @@ import com.mattermost.helpers.DatabaseHelper;
|
||||
import com.mattermost.helpers.Network;
|
||||
import com.mattermost.helpers.NotificationHelper;
|
||||
import com.mattermost.helpers.PushNotificationDataHelper;
|
||||
import com.mattermost.helpers.ResolvePromise;
|
||||
import com.mattermost.share.ShareModule;
|
||||
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
||||
import com.wix.reactnativenotifications.core.notification.PushNotification;
|
||||
@@ -31,7 +29,6 @@ public class CustomPushNotification extends PushNotification {
|
||||
|
||||
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
|
||||
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
|
||||
CustomPushNotificationHelper.createNotificationChannels(context);
|
||||
dataHelper = new PushNotificationDataHelper(context);
|
||||
|
||||
try {
|
||||
@@ -57,27 +54,31 @@ public class CustomPushNotification extends PushNotification {
|
||||
boolean isReactInit = mAppLifecycleFacade.isReactInitialized();
|
||||
|
||||
if (ackId != null && serverUrl != null) {
|
||||
notificationReceiptDelivery(ackId, serverUrl, postId, type, isIdLoaded, new ResolvePromise() {
|
||||
@Override
|
||||
public void resolve(@Nullable Object value) {
|
||||
if (isIdLoaded) {
|
||||
Bundle response = (Bundle) value;
|
||||
if (value != null) {
|
||||
response.putString("server_url", serverUrl);
|
||||
Bundle current = mNotificationProps.asBundle();
|
||||
current.putAll(response);
|
||||
mNotificationProps = createProps(current);
|
||||
}
|
||||
}
|
||||
Bundle response = ReceiptDelivery.send(ackId, serverUrl, postId, type, isIdLoaded);
|
||||
if (isIdLoaded && response != null) {
|
||||
Bundle current = mNotificationProps.asBundle();
|
||||
if (!current.containsKey("server_url")) {
|
||||
response.putString("server_url", serverUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String code, String message) {
|
||||
Log.e("ReactNative", code + ": " + message);
|
||||
}
|
||||
});
|
||||
current.putAll(response);
|
||||
mNotificationProps = createProps(current);
|
||||
}
|
||||
}
|
||||
|
||||
finishProcessingNotification(serverUrl, type, channelId, notificationId, isReactInit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpened() {
|
||||
if (mNotificationProps != null) {
|
||||
digestNotification();
|
||||
|
||||
Bundle data = mNotificationProps.asBundle();
|
||||
NotificationHelper.clearChannelOrThreadNotifications(mContext, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void finishProcessingNotification(String serverUrl, String type, String channelId, int notificationId, Boolean isReactInit) {
|
||||
switch (type) {
|
||||
case CustomPushNotificationHelper.PUSH_TYPE_MESSAGE:
|
||||
case CustomPushNotificationHelper.PUSH_TYPE_SESSION:
|
||||
@@ -118,16 +119,6 @@ public class CustomPushNotification extends PushNotification {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpened() {
|
||||
if (mNotificationProps != null) {
|
||||
digestNotification();
|
||||
|
||||
Bundle data = mNotificationProps.asBundle();
|
||||
NotificationHelper.clearChannelOrThreadNotifications(mContext, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildNotification(Integer notificationId, boolean createSummary) {
|
||||
final PendingIntent pendingIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, mNotificationProps);
|
||||
final Notification notification = buildNotification(pendingIntent);
|
||||
@@ -149,10 +140,6 @@ public class CustomPushNotification extends PushNotification {
|
||||
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle, true);
|
||||
}
|
||||
|
||||
private void notificationReceiptDelivery(String ackId, String serverUrl, String postId, String type, boolean isIdLoaded, ResolvePromise promise) {
|
||||
ReceiptDelivery.send(mContext, ackId, serverUrl, postId, type, isIdLoaded, promise);
|
||||
}
|
||||
|
||||
private void notifyReceivedToJS() {
|
||||
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
|
||||
}
|
||||
|
||||
@@ -8,28 +8,16 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.Person;
|
||||
|
||||
import android.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
import com.mattermost.helpers.*;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
|
||||
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
||||
import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
|
||||
|
||||
@@ -53,12 +41,7 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
|
||||
final int notificationId = intent.getIntExtra(CustomPushNotificationHelper.NOTIFICATION_ID, -1);
|
||||
final String serverUrl = bundle.getString("server_url");
|
||||
if (serverUrl != null) {
|
||||
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
|
||||
final String token = Credentials.getCredentialsForServerSync(reactApplicationContext, serverUrl);
|
||||
|
||||
if (token != null) {
|
||||
replyToMessage(serverUrl, token, notificationId, message);
|
||||
}
|
||||
replyToMessage(serverUrl, notificationId, message);
|
||||
} else {
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
@@ -67,7 +50,7 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
protected void replyToMessage(final String serverUrl, final String token, final int notificationId, final CharSequence message) {
|
||||
protected void replyToMessage(final String serverUrl, final int notificationId, final CharSequence message) {
|
||||
final String channelId = bundle.getString("channel_id");
|
||||
final String postId = bundle.getString("post_id");
|
||||
String rootId = bundle.getString("root_id");
|
||||
@@ -75,63 +58,53 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
|
||||
rootId = postId;
|
||||
}
|
||||
|
||||
if (token == null || serverUrl == null) {
|
||||
if (serverUrl == null) {
|
||||
onReplyFailed(notificationId);
|
||||
return;
|
||||
}
|
||||
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
String json = buildReplyPost(channelId, rootId, message.toString());
|
||||
RequestBody body = RequestBody.create(json, JSON);
|
||||
WritableMap headers = Arguments.createMap();
|
||||
headers.putString("Content-Type", "application/json");
|
||||
|
||||
|
||||
WritableMap body = Arguments.createMap();
|
||||
body.putString("channel_id", channelId);
|
||||
body.putString("message", message.toString());
|
||||
body.putString("root_id", rootId);
|
||||
|
||||
WritableMap options = Arguments.createMap();
|
||||
options.putMap("headers", headers);
|
||||
options.putMap("body", body);
|
||||
|
||||
String postsEndpoint = "/api/v4/posts?set_online=false";
|
||||
String url = String.format("%s%s", serverUrl.replaceAll("/$", ""), postsEndpoint);
|
||||
Log.i("ReactNative", String.format("Reply URL=%s", url));
|
||||
Request request = new Request.Builder()
|
||||
.header("Authorization", String.format("Bearer %s", token))
|
||||
.header("Content-Type", "application/json")
|
||||
.url(url)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
client.newCall(request).enqueue(new okhttp3.Callback() {
|
||||
Network.post(serverUrl, postsEndpoint, options, new ResolvePromise() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
Log.i("ReactNative", String.format("Reply FAILED exception %s", e.getMessage()));
|
||||
public void resolve(@Nullable Object value) {
|
||||
if (value != null) {
|
||||
onReplySuccess(notificationId, message);
|
||||
Log.i("ReactNative", "Reply SUCCESS");
|
||||
} else {
|
||||
Log.i("ReactNative", "Reply FAILED resolved without value");
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(Throwable reason) {
|
||||
Log.i("ReactNative", String.format("Reply FAILED exception %s", reason.getMessage()));
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull final Response response) throws IOException {
|
||||
if (response.isSuccessful()) {
|
||||
onReplySuccess(notificationId, message);
|
||||
Log.i("ReactNative", "Reply SUCCESS");
|
||||
} else {
|
||||
Log.i("ReactNative",
|
||||
String.format("Reply FAILED status %s BODY %s",
|
||||
response.code(),
|
||||
Objects.requireNonNull(response.body()).string()
|
||||
)
|
||||
);
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
public void reject(String code, String message) {
|
||||
Log.i("ReactNative",
|
||||
String.format("Reply FAILED status %s BODY %s", code, message)
|
||||
);
|
||||
onReplyFailed(notificationId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected String buildReplyPost(String channelId, String rootId, String message) {
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("channel_id", channelId);
|
||||
json.put("message", message);
|
||||
json.put("root_id", rootId);
|
||||
return json.toString();
|
||||
} catch(JSONException e) {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
protected void onReplyFailed(int notificationId) {
|
||||
recreateNotification(notificationId, "Message failed to send.");
|
||||
}
|
||||
|
||||
@@ -1,135 +1,60 @@
|
||||
package com.mattermost.rnbeta;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.System;
|
||||
import java.util.Objects;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.mattermost.helpers.*;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public class ReceiptDelivery {
|
||||
private static final int[] FIBONACCI_BACKOFF = new int[] { 0, 1, 2, 3, 5, 8 };
|
||||
private static final String[] ackKeys = new String[]{"post_id", "root_id", "category", "message", "team_id", "channel_id", "channel_name", "type", "sender_id", "sender_name", "version"};
|
||||
|
||||
public static void send(Context context, final String ackId, final String serverUrl, final String postId, final String type, final boolean isIdLoaded, ResolvePromise promise) {
|
||||
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
|
||||
final String token = Credentials.getCredentialsForServerSync(reactApplicationContext, serverUrl);
|
||||
public static Bundle send(final String ackId, final String serverUrl, final String postId, final String type, final boolean isIdLoaded) {
|
||||
Log.i("ReactNative", String.format("Send receipt delivery ACK=%s TYPE=%s to URL=%s with ID-LOADED=%s", ackId, type, serverUrl, isIdLoaded));
|
||||
execute(serverUrl, postId, token, ackId, type, isIdLoaded, promise);
|
||||
}
|
||||
WritableMap options = Arguments.createMap();
|
||||
WritableMap headers = Arguments.createMap();
|
||||
WritableMap body = Arguments.createMap();
|
||||
headers.putString("Content-Type", "application/json");
|
||||
options.putMap("headers", headers);
|
||||
body.putString("id", ackId);
|
||||
body.putDouble("received_at", System.currentTimeMillis());
|
||||
body.putString("platform", "android");
|
||||
body.putString("type", type);
|
||||
body.putString("post_id", postId);
|
||||
body.putBoolean("is_id_loaded", isIdLoaded);
|
||||
options.putMap("body", body);
|
||||
|
||||
protected static void execute(String serverUrl, String postId, String token, String ackId, String type, boolean isIdLoaded, ResolvePromise promise) {
|
||||
if (token == null) {
|
||||
promise.reject("Receipt delivery failure", "Invalid token");
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverUrl == null) {
|
||||
promise.reject("Receipt delivery failure", "Invalid server URL");
|
||||
}
|
||||
|
||||
JSONObject json;
|
||||
long receivedAt = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
json = new JSONObject();
|
||||
json.put("id", ackId);
|
||||
json.put("received_at", receivedAt);
|
||||
json.put("platform", "android");
|
||||
json.put("type", type);
|
||||
json.put("post_id", postId);
|
||||
json.put("is_id_loaded", isIdLoaded);
|
||||
} catch (JSONException e) {
|
||||
Log.e("ReactNative", "Receipt delivery failed to build json payload");
|
||||
promise.reject("Receipt delivery failure", e.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
final HttpUrl url;
|
||||
if (serverUrl != null) {
|
||||
url = HttpUrl.parse(
|
||||
String.format("%s/api/v4/notifications/ack", serverUrl.replaceAll("/$", "")));
|
||||
if (url != null) {
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
RequestBody body = RequestBody.create(json.toString(), JSON);
|
||||
Request request = new Request.Builder()
|
||||
.header("Authorization", String.format("Bearer %s", token))
|
||||
.header("Content-Type", "application/json")
|
||||
.url(url)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
makeServerRequest(client, request, isIdLoaded, 0, promise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeServerRequest(OkHttpClient client, Request request, Boolean isIdLoaded, int reRequestCount, ResolvePromise promise) {
|
||||
try {
|
||||
Response response = client.newCall(request).execute();
|
||||
try (Response response = Network.postSync(serverUrl, "api/v4/notifications/ack", options)) {
|
||||
String responseBody = Objects.requireNonNull(response.body()).string();
|
||||
if (response.code() != 200) {
|
||||
switch (response.code()) {
|
||||
case 302:
|
||||
promise.reject("Receipt delivery failure", "StatusFound");
|
||||
return;
|
||||
case 400:
|
||||
promise.reject("Receipt delivery failure", "StatusBadRequest");
|
||||
return;
|
||||
case 401:
|
||||
promise.reject("Receipt delivery failure", "Unauthorized");
|
||||
case 403:
|
||||
promise.reject("Receipt delivery failure", "Forbidden");
|
||||
return;
|
||||
case 500:
|
||||
promise.reject("Receipt delivery failure", "StatusInternalServerError");
|
||||
return;
|
||||
case 501:
|
||||
promise.reject("Receipt delivery failure", "StatusNotImplemented");
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception(responseBody);
|
||||
}
|
||||
|
||||
JSONObject jsonResponse = new JSONObject(responseBody);
|
||||
return parseAckResponse(jsonResponse);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Bundle parseAckResponse(JSONObject jsonResponse) {
|
||||
try {
|
||||
Bundle bundle = new Bundle();
|
||||
String[] keys = new String[]{"post_id", "root_id", "category", "message", "team_id", "channel_id", "channel_name", "type", "sender_id", "sender_name", "version"};
|
||||
for (String key : keys) {
|
||||
for (String key : ackKeys) {
|
||||
if (jsonResponse.has(key)) {
|
||||
bundle.putString(key, jsonResponse.getString(key));
|
||||
}
|
||||
}
|
||||
promise.resolve(bundle);
|
||||
return bundle;
|
||||
} catch (Exception e) {
|
||||
Log.e("ReactNative", "Receipt delivery failed to send");
|
||||
if (isIdLoaded) {
|
||||
try {
|
||||
reRequestCount++;
|
||||
if (reRequestCount < FIBONACCI_BACKOFF.length) {
|
||||
Log.i("ReactNative", "Retry attempt " + reRequestCount + " with backoff delay: " + FIBONACCI_BACKOFF[reRequestCount] + " seconds");
|
||||
Thread.sleep(FIBONACCI_BACKOFF[reRequestCount] * 1000);
|
||||
makeServerRequest(client, request, true, reRequestCount, promise);
|
||||
}
|
||||
} catch(InterruptedException ie) {
|
||||
ie.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
promise.reject("Receipt delivery failure", e.toString());
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.mattermost.flipper;
|
||||
|
||||
import android.content.Context;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
/**
|
||||
* Class responsible of loading Flipper inside your React Native application. This is the release
|
||||
* flavor of it so it's empty as we don't want to load Flipper.
|
||||
*/
|
||||
public class ReactNativeFlipper {
|
||||
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||
// Do nothing as we don't want to initialize Flipper on Release.
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import {queryMyTeams} from '@queries/servers/team';
|
||||
import {isDMorGM} from '@utils/channel';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
import type {Model} from '@nozbe/watermelondb';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
export const deleteCategory = async (serverUrl: string, categoryId: string) => {
|
||||
@@ -40,7 +39,7 @@ export async function storeCategories(serverUrl: string, categories: CategoryWit
|
||||
}
|
||||
|
||||
if (models.length > 0) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'storeCategories');
|
||||
}
|
||||
|
||||
return {models};
|
||||
@@ -80,7 +79,6 @@ export async function addChannelToDefaultCategory(serverUrl: string, channel: Ch
|
||||
return {error: 'no current user id'};
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
const categoriesWithChannels: CategoryWithChannels[] = [];
|
||||
|
||||
if (isDMorGM(channel)) {
|
||||
@@ -100,13 +98,12 @@ export async function addChannelToDefaultCategory(serverUrl: string, channel: Ch
|
||||
cwc.channel_ids.unshift(channel.id);
|
||||
categoriesWithChannels.push(cwc);
|
||||
}
|
||||
|
||||
const ccModels = await prepareCategoryChannels(operator, categoriesWithChannels);
|
||||
models.push(...ccModels);
|
||||
}
|
||||
|
||||
const models = await prepareCategoryChannels(operator, categoriesWithChannels);
|
||||
|
||||
if (models.length && !prepareRecordsOnly) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'addChannelToDefaultCategory');
|
||||
}
|
||||
|
||||
return {models};
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
prepareDeleteChannel, prepareMyChannelsForTeam, queryAllMyChannel,
|
||||
getMyChannel, getChannelById, queryUsersOnChannel, queryUserChannelsByTypes,
|
||||
} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {queryDisplayNamePreferences} from '@queries/servers/preference';
|
||||
import {prepareCommonSystemValues, PrepareCommonSystemValuesArgs, getCommonSystemValues, getCurrentTeamId, setCurrentChannelId, getCurrentUserId, getConfig, getLicense} from '@queries/servers/system';
|
||||
import {addChannelToTeamHistory, addTeamToTeamHistory, getTeamById, removeChannelFromTeamHistory} from '@queries/servers/team';
|
||||
import {getCurrentUser, queryUsersById} from '@queries/servers/user';
|
||||
@@ -82,7 +82,7 @@ export async function switchToChannel(serverUrl: string, channelId: string, team
|
||||
}
|
||||
|
||||
if (models.length && !prepareRecordsOnly) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'switchToChannel');
|
||||
}
|
||||
|
||||
if (isTabletDevice) {
|
||||
@@ -124,7 +124,7 @@ export async function removeCurrentUserFromChannel(serverUrl: string, channelId:
|
||||
await removeChannelFromTeamHistory(operator, teamId, channel.id, false);
|
||||
|
||||
if (models.length && !prepareRecordsOnly) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'removeCurrentUserFromChannel');
|
||||
}
|
||||
}
|
||||
return {models};
|
||||
@@ -145,7 +145,7 @@ export async function setChannelDeleteAt(serverUrl: string, channelId: string, d
|
||||
const model = channel.prepareUpdate((c) => {
|
||||
c.deleteAt = deleteAt;
|
||||
});
|
||||
await operator.batchRecords([model]);
|
||||
await operator.batchRecords([model], 'setChannelDeleteAt');
|
||||
} catch (error) {
|
||||
logError('FAILED TO BATCH CHANGES FOR CHANNEL DELETE AT', error);
|
||||
}
|
||||
@@ -179,7 +179,7 @@ export async function markChannelAsViewed(serverUrl: string, channelId: string,
|
||||
});
|
||||
PushNotifications.removeChannelNotifications(serverUrl, channelId);
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([member]);
|
||||
await operator.batchRecords([member], 'markChannelAsViewed');
|
||||
}
|
||||
|
||||
return {member};
|
||||
@@ -206,7 +206,7 @@ export async function markChannelAsUnread(serverUrl: string, channelId: string,
|
||||
m.isUnread = true;
|
||||
});
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([member]);
|
||||
await operator.batchRecords([member], 'markChannelAsUnread');
|
||||
}
|
||||
|
||||
return {member};
|
||||
@@ -226,7 +226,7 @@ export async function resetMessageCount(serverUrl: string, channelId: string) {
|
||||
member.prepareUpdate((m) => {
|
||||
m.messageCount = 0;
|
||||
});
|
||||
await operator.batchRecords([member]);
|
||||
await operator.batchRecords([member], 'resetMessageCount');
|
||||
|
||||
return member;
|
||||
} catch (error) {
|
||||
@@ -254,7 +254,7 @@ export async function storeMyChannelsForTeam(serverUrl: string, teamId: string,
|
||||
}
|
||||
|
||||
if (flattenedModels.length) {
|
||||
await operator.batchRecords(flattenedModels);
|
||||
await operator.batchRecords(flattenedModels, 'storeMyChannelsForTeam');
|
||||
}
|
||||
|
||||
return {models: flattenedModels};
|
||||
@@ -273,7 +273,7 @@ export async function updateMyChannelFromWebsocket(serverUrl: string, channelMem
|
||||
m.roles = channelMember.roles;
|
||||
});
|
||||
if (!prepareRecordsOnly) {
|
||||
operator.batchRecords([member]);
|
||||
operator.batchRecords([member], 'updateMyChannelFromWebsocket');
|
||||
}
|
||||
}
|
||||
return {model: member};
|
||||
@@ -293,7 +293,7 @@ export async function updateChannelInfoFromChannel(serverUrl: string, channel: C
|
||||
}],
|
||||
prepareRecordsOnly: true});
|
||||
if (!prepareRecordsOnly) {
|
||||
operator.batchRecords(newInfo);
|
||||
operator.batchRecords(newInfo, 'updateChannelInfoFromChannel');
|
||||
}
|
||||
return {model: newInfo};
|
||||
} catch (error) {
|
||||
@@ -317,7 +317,7 @@ export async function updateLastPostAt(serverUrl: string, channelId: string, las
|
||||
});
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([myChannel]);
|
||||
await operator.batchRecords([myChannel], 'updateLastPostAt');
|
||||
}
|
||||
|
||||
return {member: myChannel};
|
||||
@@ -345,7 +345,7 @@ export async function updateMyChannelLastFetchedAt(serverUrl: string, channelId:
|
||||
});
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([myChannel]);
|
||||
await operator.batchRecords([myChannel], 'updateMyChannelLastFetchedAt');
|
||||
}
|
||||
|
||||
return {member: myChannel};
|
||||
@@ -369,7 +369,7 @@ export async function updateChannelsDisplayName(serverUrl: string, channels: Cha
|
||||
|
||||
const license = await getLicense(database);
|
||||
const config = await getConfig(database);
|
||||
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
|
||||
const preferences = await queryDisplayNamePreferences(database, Preferences.NAME_NAME_FORMAT).fetch();
|
||||
const displaySettings = getTeammateNameDisplaySetting(preferences, config.LockTeammateNameDisplay, config.TeammateNameDisplay, license);
|
||||
const models: Model[] = [];
|
||||
for await (const channel of channels) {
|
||||
@@ -403,7 +403,7 @@ export async function updateChannelsDisplayName(serverUrl: string, channels: Cha
|
||||
}
|
||||
|
||||
if (models.length && !prepareRecordsOnly) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'updateChannelsDisplayName');
|
||||
}
|
||||
|
||||
return {models};
|
||||
|
||||
@@ -26,7 +26,7 @@ export async function updateDraftFile(serverUrl: string, channelId: string, root
|
||||
});
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([draft]);
|
||||
await operator.batchRecords([draft], 'updateDraftFile');
|
||||
}
|
||||
|
||||
return {draft};
|
||||
@@ -58,7 +58,7 @@ export async function removeDraftFile(serverUrl: string, channelId: string, root
|
||||
}
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([draft]);
|
||||
await operator.batchRecords([draft], 'removeDraftFile');
|
||||
}
|
||||
|
||||
return {draft};
|
||||
@@ -99,7 +99,7 @@ export async function updateDraftMessage(serverUrl: string, channelId: string, r
|
||||
}
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([draft]);
|
||||
await operator.batchRecords([draft], 'updateDraftMessage');
|
||||
}
|
||||
|
||||
return {draft};
|
||||
@@ -129,7 +129,7 @@ export async function addFilesToDraft(serverUrl: string, channelId: string, root
|
||||
});
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([draft]);
|
||||
await operator.batchRecords([draft], 'addFilesToDraft');
|
||||
}
|
||||
|
||||
return {draft};
|
||||
|
||||
@@ -127,14 +127,14 @@ export async function removePost(serverUrl: string, post: PostModel | Post) {
|
||||
}
|
||||
|
||||
if (removeModels.length) {
|
||||
await operator.batchRecords(removeModels);
|
||||
await operator.batchRecords(removeModels, 'removePost (combined user activity)');
|
||||
}
|
||||
} else {
|
||||
const postModel = await getPostById(database, post.id);
|
||||
if (postModel) {
|
||||
const preparedPost = await prepareDeletePost(postModel);
|
||||
if (preparedPost.length) {
|
||||
await operator.batchRecords(preparedPost);
|
||||
await operator.batchRecords(preparedPost, 'removePost');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,7 @@ export async function markPostAsDeleted(serverUrl: string, post: Post, prepareRe
|
||||
});
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([dbPost]);
|
||||
await operator.batchRecords([dbPost], 'markPostAsDeleted');
|
||||
}
|
||||
return {model};
|
||||
} catch (error) {
|
||||
@@ -229,7 +229,7 @@ export async function storePostsForChannel(
|
||||
}
|
||||
|
||||
if (models.length && !prepareRecordsOnly) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'storePostsForChannel');
|
||||
}
|
||||
|
||||
return {models};
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function removeUserFromTeam(serverUrl: string, teamId: string) {
|
||||
models.push(...system);
|
||||
}
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'removeUserFromTeam');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function addSearchToTeamSearchHistory(serverUrl: string, teamId: st
|
||||
}
|
||||
}
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'addSearchToTeamHistory');
|
||||
return {searchModel};
|
||||
} catch (error) {
|
||||
logError('Failed addSearchToTeamSearchHistory', error);
|
||||
|
||||
@@ -40,7 +40,7 @@ export const switchToGlobalThreads = async (serverUrl: string, teamId?: string,
|
||||
models.push(...history);
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'switchToGlobalThreads');
|
||||
}
|
||||
|
||||
const isTabletDevice = await isTablet();
|
||||
@@ -84,7 +84,7 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
|
||||
currentChannelId: channel.id,
|
||||
});
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'switchToThread');
|
||||
}
|
||||
} else {
|
||||
const modelPromises: Array<Promise<Model[]>> = [];
|
||||
@@ -97,7 +97,7 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
|
||||
modelPromises.push(prepareCommonSystemValues(operator, commonValues));
|
||||
const models = (await Promise.all(modelPromises)).flat();
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'switchToThread');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ export async function createThreadFromNewPost(serverUrl: string, post: Post, pre
|
||||
}
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'createThreadFromNewPost');
|
||||
}
|
||||
|
||||
return {models};
|
||||
@@ -257,7 +257,7 @@ export async function processReceivedThreads(serverUrl: string, threads: Thread[
|
||||
}
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'processReceivedThreads');
|
||||
}
|
||||
return {models};
|
||||
} catch (error) {
|
||||
@@ -277,7 +277,7 @@ export async function markTeamThreadsAsRead(serverUrl: string, teamId: string, p
|
||||
record.viewedAt = Date.now();
|
||||
}));
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'markTeamThreadsAsRead');
|
||||
}
|
||||
return {models};
|
||||
} catch (error) {
|
||||
@@ -300,7 +300,7 @@ export async function markThreadAsViewed(serverUrl: string, threadId: string, pr
|
||||
});
|
||||
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([thread]);
|
||||
await operator.batchRecords([thread], 'markThreadAsViewed');
|
||||
}
|
||||
|
||||
return {model: thread};
|
||||
@@ -327,7 +327,7 @@ export async function updateThread(serverUrl: string, threadId: string, updatedT
|
||||
record.unreadReplies = updatedThread.unread_replies ?? record.unreadReplies;
|
||||
});
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords([model]);
|
||||
await operator.batchRecords([model], 'updateThread');
|
||||
}
|
||||
return {model};
|
||||
} catch (error) {
|
||||
@@ -341,7 +341,7 @@ export async function updateTeamThreadsSync(serverUrl: string, data: TeamThreads
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const models = await operator.handleTeamThreadsSync({data: [data], prepareRecordsOnly});
|
||||
if (!prepareRecordsOnly) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'updateTeamThreadsSync');
|
||||
}
|
||||
return {models};
|
||||
} catch (error) {
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function setCurrentUserStatusOffline(serverUrl: string) {
|
||||
}
|
||||
|
||||
user.prepareStatus(General.OFFLINE);
|
||||
await operator.batchRecords([user]);
|
||||
await operator.batchRecords([user], 'setCurrentUserStatusOffline');
|
||||
return null;
|
||||
} catch (error) {
|
||||
logError('Failed setCurrentUserStatusOffline', error);
|
||||
@@ -54,7 +54,7 @@ export async function updateLocalCustomStatus(serverUrl: string, user: UserModel
|
||||
}
|
||||
}
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'updateLocalCustomStatus');
|
||||
|
||||
return {};
|
||||
} catch (error) {
|
||||
@@ -97,27 +97,37 @@ export const updateRecentCustomStatuses = async (serverUrl: string, customStatus
|
||||
export const updateLocalUser = async (
|
||||
serverUrl: string,
|
||||
userDetails: Partial<UserProfile> & { status?: string},
|
||||
userId?: string,
|
||||
) => {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const user = await getCurrentUser(database);
|
||||
|
||||
let user: UserModel | undefined;
|
||||
|
||||
if (userId) {
|
||||
user = await getUserById(database, userId);
|
||||
} else {
|
||||
user = await getCurrentUser(database);
|
||||
}
|
||||
|
||||
if (user) {
|
||||
const u = user;
|
||||
await database.write(async () => {
|
||||
await user.update((userRecord: UserModel) => {
|
||||
userRecord.authService = userDetails.auth_service ?? user.authService;
|
||||
userRecord.email = userDetails.email ?? user.email;
|
||||
userRecord.firstName = userDetails.first_name ?? user.firstName;
|
||||
userRecord.lastName = userDetails.last_name ?? user.lastName;
|
||||
userRecord.lastPictureUpdate = userDetails.last_picture_update ?? user.lastPictureUpdate;
|
||||
userRecord.locale = userDetails.locale ?? user.locale;
|
||||
userRecord.nickname = userDetails.nickname ?? user.nickname;
|
||||
userRecord.notifyProps = userDetails.notify_props ?? user.notifyProps;
|
||||
userRecord.position = userDetails?.position ?? user.position;
|
||||
userRecord.props = userDetails.props ?? user.props;
|
||||
userRecord.roles = userDetails.roles ?? user.roles;
|
||||
userRecord.status = userDetails?.status ?? user.status;
|
||||
userRecord.timezone = userDetails.timezone ?? user.timezone;
|
||||
userRecord.username = userDetails.username ?? user.username;
|
||||
await u.update((userRecord: UserModel) => {
|
||||
userRecord.authService = userDetails.auth_service ?? u.authService;
|
||||
userRecord.email = userDetails.email ?? u.email;
|
||||
userRecord.firstName = userDetails.first_name ?? u.firstName;
|
||||
userRecord.lastName = userDetails.last_name ?? u.lastName;
|
||||
userRecord.lastPictureUpdate = userDetails.last_picture_update ?? u.lastPictureUpdate;
|
||||
userRecord.locale = userDetails.locale ?? u.locale;
|
||||
userRecord.nickname = userDetails.nickname ?? u.nickname;
|
||||
userRecord.notifyProps = userDetails.notify_props ?? u.notifyProps;
|
||||
userRecord.position = userDetails?.position ?? u.position;
|
||||
userRecord.props = userDetails.props ?? u.props;
|
||||
userRecord.roles = userDetails.roles ?? u.roles;
|
||||
userRecord.status = userDetails?.status ?? u.status;
|
||||
userRecord.timezone = userDetails.timezone ?? u.timezone;
|
||||
userRecord.username = userDetails.username ?? u.username;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {DeviceEventEmitter} from 'react-native';
|
||||
import {addChannelToDefaultCategory, storeCategories} from '@actions/local/category';
|
||||
import {removeCurrentUserFromChannel, setChannelDeleteAt, storeMyChannelsForTeam, switchToChannel} from '@actions/local/channel';
|
||||
import {switchToGlobalThreads} from '@actions/local/thread';
|
||||
import {updateLocalUser} from '@actions/local/user';
|
||||
import {loadCallForChannel} from '@calls/actions/calls';
|
||||
import {DeepLink, Events, General, Preferences, Screens} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
@@ -15,8 +16,8 @@ import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
|
||||
import AppsManager from '@managers/apps_manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getActiveServer} from '@queries/app/servers';
|
||||
import {prepareMyChannelsForTeam, getChannelById, getChannelByName, getMyChannel, getChannelInfo, queryMyChannelSettingsByIds, getMembersCountByChannelsId} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {prepareMyChannelsForTeam, getChannelById, getChannelByName, getMyChannel, getChannelInfo, queryMyChannelSettingsByIds, getMembersCountByChannelsId, deleteChannelMembership, queryChannelsById} from '@queries/servers/channel';
|
||||
import {queryDisplayNamePreferences} from '@queries/servers/preference';
|
||||
import {getCommonSystemValues, getConfig, getCurrentChannelId, getCurrentTeamId, getCurrentUserId, getLicense, setCurrentChannelId, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {getNthLastChannelFromTeam, getMyTeamById, getTeamByName, queryMyTeams, removeChannelFromTeamHistory} from '@queries/servers/team';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
@@ -35,9 +36,10 @@ import {setDirectChannelVisible} from './preference';
|
||||
import {fetchRolesIfNeeded} from './role';
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
import {addCurrentUserToTeam, fetchTeamByName, removeCurrentUserFromTeam} from './team';
|
||||
import {fetchProfilesInGroupChannels, fetchProfilesPerChannels, fetchUsersByIds, updateUsersNoLongerVisible} from './user';
|
||||
import {fetchProfilesInChannel, fetchProfilesInGroupChannels, fetchProfilesPerChannels, fetchUsersByIds, updateUsersNoLongerVisible} from './user';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type {Model} from '@nozbe/watermelondb';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
@@ -49,6 +51,99 @@ export type MyChannelsRequest = {
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
export type ChannelMembersRequest = {
|
||||
members?: ChannelMembership[];
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
export async function removeMemberFromChannel(serverUrl: string, channelId: string, userId: string) {
|
||||
try {
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
|
||||
await client.removeFromChannel(userId, channelId);
|
||||
await deleteChannelMembership(operator, userId, channelId);
|
||||
|
||||
return {error: undefined};
|
||||
} catch (error) {
|
||||
logError('removeMemberFromChannel', error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchChannelMembersByIds(serverUrl: string, channelId: string, userIds: string[], fetchOnly = false): Promise<ChannelMembersRequest> {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const members = await client.getChannelMembersByIds(channelId, userIds);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
if (operator && members.length) {
|
||||
const memberships = members.map((u) => ({
|
||||
channel_id: channelId,
|
||||
user_id: u.user_id,
|
||||
scheme_admin: u.scheme_admin,
|
||||
}));
|
||||
await operator.handleChannelMembership({
|
||||
channelMemberships: memberships,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {members};
|
||||
} catch (error) {
|
||||
logError('fetchChannelMembersByIds', error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
export async function updateChannelMemberSchemeRoles(serverUrl: string, channelId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean, fetchOnly = false) {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
await client.updateChannelMemberSchemeRoles(channelId, userId, isSchemeUser, isSchemeAdmin);
|
||||
|
||||
if (!fetchOnly) {
|
||||
return getMemberInChannel(serverUrl, channelId, userId);
|
||||
}
|
||||
|
||||
return {error: undefined};
|
||||
} catch (error) {
|
||||
logError('updateChannelMemberSchemeRoles', error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMemberInChannel(serverUrl: string, channelId: string, userId: string, fetchOnly = false) {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const member = await client.getMemberInChannel(channelId, userId);
|
||||
|
||||
if (!fetchOnly) {
|
||||
updateLocalUser(serverUrl, member, userId);
|
||||
}
|
||||
return {member, error: undefined};
|
||||
} catch (error) {
|
||||
logError('getMemberInChannel', error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchChannelMemberships(serverUrl: string, channelId: string, options: GetUsersOptions, fetchOnly = false) {
|
||||
const {users = []} = await fetchProfilesInChannel(serverUrl, channelId, undefined, options, fetchOnly);
|
||||
const userIds = users.map((u) => u.id);
|
||||
|
||||
// MM-49896 https://mattermost.atlassian.net/browse/MM-49896
|
||||
// We are not sure the getChannelMembers API returns the same members
|
||||
// from getProfilesInChannel. This guarantees a 1:1 match of the
|
||||
// user IDs
|
||||
const {members = []} = await fetchChannelMembersByIds(serverUrl, channelId, userIds, true);
|
||||
return {users, members};
|
||||
}
|
||||
|
||||
export async function addMembersToChannel(serverUrl: string, channelId: string, userIds: string[], postRootId = '', fetchOnly = false) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
@@ -79,7 +174,7 @@ export async function addMembersToChannel(serverUrl: string, channelId: string,
|
||||
}));
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
await operator.batchRecords(models.flat());
|
||||
await operator.batchRecords(models.flat(), 'addMembersToChannel');
|
||||
}
|
||||
return {channelMemberships};
|
||||
} catch (error) {
|
||||
@@ -156,7 +251,7 @@ export async function createChannel(serverUrl: string, displayName: string, purp
|
||||
models.push(...categoriesModels.models);
|
||||
}
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'createChannel');
|
||||
}
|
||||
fetchChannelStats(serverUrl, channelData.id, false);
|
||||
EphemeralStore.creatingChannel = false;
|
||||
@@ -201,7 +296,7 @@ export async function patchChannel(serverUrl: string, channelPatch: Partial<Chan
|
||||
models.push(channel);
|
||||
}
|
||||
if (models?.length) {
|
||||
await operator.batchRecords(models.flat());
|
||||
await operator.batchRecords(models.flat(), 'patchChannel');
|
||||
}
|
||||
return {channel: channelData};
|
||||
} catch (error) {
|
||||
@@ -248,7 +343,7 @@ export async function leaveChannel(serverUrl: string, channelId: string) {
|
||||
models.push(...removeUserModels);
|
||||
}
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'leaveChannel');
|
||||
|
||||
if (isTabletDevice) {
|
||||
switchToLastChannel(serverUrl);
|
||||
@@ -298,7 +393,7 @@ export async function fetchChannelCreator(serverUrl: string, channelId: string,
|
||||
}));
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
await operator.batchRecords(models.flat());
|
||||
await operator.batchRecords(models.flat(), 'fetchChannelCreator');
|
||||
}
|
||||
|
||||
return {user};
|
||||
@@ -389,7 +484,7 @@ export async function fetchMyChannelsForTeam(serverUrl: string, teamId: string,
|
||||
const {models: catModels} = await storeCategories(serverUrl, categories, true, true); // Re-sync
|
||||
const models = (chModels || []).concat(catModels || []);
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'fetchMyChannelsForTeam');
|
||||
}
|
||||
setTeamLoading(serverUrl, false);
|
||||
}
|
||||
@@ -438,38 +533,37 @@ export async function fetchMyChannel(serverUrl: string, teamId: string, channelI
|
||||
}
|
||||
|
||||
export async function fetchMissingDirectChannelsInfo(serverUrl: string, directChannels: Channel[], locale?: string, teammateDisplayNameSetting?: string, currentUserId?: string, fetchOnly = false) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
const displayNameByChannel: Record<string, string> = {};
|
||||
const users: UserProfile[] = [];
|
||||
const updatedChannels = new Set<Channel>();
|
||||
|
||||
const dms: Channel[] = [];
|
||||
const dmIds: string[] = [];
|
||||
const dmWithoutDisplayName = new Set<string>();
|
||||
const gms: Channel[] = [];
|
||||
for (const c of directChannels) {
|
||||
if (c.type === General.DM_CHANNEL) {
|
||||
dms.push(c);
|
||||
dmIds.push(c.id);
|
||||
if (!c.display_name) {
|
||||
dmWithoutDisplayName.add(c.id);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
gms.push(c);
|
||||
}
|
||||
|
||||
try {
|
||||
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const displayNameByChannel: Record<string, string> = {};
|
||||
const users: UserProfile[] = [];
|
||||
const updatedChannels = new Set<Channel>();
|
||||
|
||||
const dms: Channel[] = [];
|
||||
const dmIds: string[] = [];
|
||||
const dmWithoutDisplayName = new Set<string>();
|
||||
const gms: Channel[] = [];
|
||||
const channelIds = new Set(directChannels.map((c) => c.id));
|
||||
const storedChannels = await queryChannelsById(database, Array.from(channelIds)).fetch();
|
||||
const storedChannelsMap = new Map(storedChannels.map((c) => [c.id, c]));
|
||||
|
||||
for (const c of directChannels) {
|
||||
if (c.type === General.DM_CHANNEL) {
|
||||
dms.push(c);
|
||||
dmIds.push(c.id);
|
||||
if (!c.display_name && !storedChannelsMap.get(c.id)?.displayName) {
|
||||
dmWithoutDisplayName.add(c.id);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
gms.push(c);
|
||||
}
|
||||
|
||||
const currentUser = await getCurrentUser(database);
|
||||
|
||||
// let's filter those channels that we already have the users
|
||||
const membersCount = await getMembersCountByChannelsId(database, dmIds);
|
||||
const profileChannelsToFetch = dmIds.filter((id) => membersCount[id] <= 1 || dmWithoutDisplayName.has(id));
|
||||
const profileChannelsToFetch = dmIds.filter((id) => membersCount[id] <= 1 && dmWithoutDisplayName.has(id));
|
||||
const results = await Promise.all([
|
||||
profileChannelsToFetch.length ? fetchProfilesPerChannels(serverUrl, profileChannelsToFetch, currentUserId, false) : Promise.resolve({data: undefined}),
|
||||
fetchProfilesInGroupChannels(serverUrl, gms.map((c) => c.id), false),
|
||||
@@ -515,7 +609,7 @@ export async function fetchMissingDirectChannelsInfo(serverUrl: string, directCh
|
||||
}
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
await operator.batchRecords(models.flat());
|
||||
await operator.batchRecords(models.flat(), 'fetchMissingDirectChannelInfo');
|
||||
}
|
||||
|
||||
return {directChannels: updatedChannelsArray, users};
|
||||
@@ -530,7 +624,7 @@ export async function fetchDirectChannelsInfo(serverUrl: string, directChannels:
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).fetch();
|
||||
const preferences = await queryDisplayNamePreferences(database).fetch();
|
||||
const config = await getConfig(database);
|
||||
const license = await getLicense(database);
|
||||
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences, config?.LockTeammateNameDisplay, config?.TeammateNameDisplay, license);
|
||||
@@ -593,7 +687,7 @@ export async function joinChannel(serverUrl: string, teamId: string, channelId?:
|
||||
}
|
||||
if (flattenedModels?.length > 0) {
|
||||
try {
|
||||
await operator.batchRecords(flattenedModels);
|
||||
await operator.batchRecords(flattenedModels, 'joinChannel');
|
||||
} catch {
|
||||
logError('FAILED TO BATCH CHANNELS');
|
||||
}
|
||||
@@ -776,7 +870,7 @@ export async function createDirectChannel(serverUrl: string, userId: string, dis
|
||||
if (displayName) {
|
||||
created.display_name = displayName;
|
||||
} else {
|
||||
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
|
||||
const preferences = await queryDisplayNamePreferences(database, Preferences.NAME_NAME_FORMAT).fetch();
|
||||
const license = await getLicense(database);
|
||||
const config = await getConfig(database);
|
||||
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config.LockTeammateNameDisplay, config.TeammateNameDisplay, license);
|
||||
@@ -819,7 +913,7 @@ export async function createDirectChannel(serverUrl: string, userId: string, dis
|
||||
models.push(...userModels);
|
||||
}
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'createDirectChannel');
|
||||
EphemeralStore.creatingDMorGMTeammates = [];
|
||||
fetchRolesIfNeeded(serverUrl, member.roles.split(' '));
|
||||
return {data: created};
|
||||
@@ -918,7 +1012,7 @@ export async function createGroupChannel(serverUrl: string, userIds: string[]) {
|
||||
return {data: created};
|
||||
}
|
||||
|
||||
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
|
||||
const preferences = await queryDisplayNamePreferences(database, Preferences.NAME_NAME_FORMAT).fetch();
|
||||
const license = await getLicense(database);
|
||||
const config = await getConfig(database);
|
||||
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config.LockTeammateNameDisplay, config.TeammateNameDisplay, license);
|
||||
@@ -953,7 +1047,7 @@ export async function createGroupChannel(serverUrl: string, userIds: string[]) {
|
||||
}
|
||||
|
||||
models.push(...userModels);
|
||||
operator.batchRecords(models);
|
||||
operator.batchRecords(models, 'createGroupChannel');
|
||||
}
|
||||
}
|
||||
EphemeralStore.creatingDMorGMTeammates = [];
|
||||
@@ -1298,7 +1392,7 @@ export const convertChannelToPrivate = async (serverUrl: string, channelId: stri
|
||||
channel.prepareUpdate((c) => {
|
||||
c.type = General.PRIVATE_CHANNEL;
|
||||
});
|
||||
await operator.batchRecords([channel]);
|
||||
await operator.batchRecords([channel], 'convertChannelToPrivate');
|
||||
}
|
||||
return {error: undefined};
|
||||
} catch (error) {
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function appEntry(serverUrl: string, since = 0, isUpgrade = false)
|
||||
// clear lastUnreadChannelId
|
||||
const removeLastUnreadChannelId = await prepareCommonSystemValues(operator, {lastUnreadChannelId: ''});
|
||||
if (removeLastUnreadChannelId) {
|
||||
await operator.batchRecords(removeLastUnreadChannelId);
|
||||
await operator.batchRecords(removeLastUnreadChannelId, 'appEntry - removeLastUnreadChannelId');
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
@@ -58,14 +58,14 @@ export async function appEntry(serverUrl: string, since = 0, isUpgrade = false)
|
||||
currentChannelId: isTabletDevice ? initialChannelId : undefined,
|
||||
});
|
||||
if (me?.length) {
|
||||
await operator.batchRecords(me);
|
||||
await operator.batchRecords(me, 'appEntry - upgrade store me');
|
||||
}
|
||||
}
|
||||
|
||||
await handleEntryAfterLoadNavigation(serverUrl, teamData.memberships || [], chData?.memberships || [], currentTeamId, currentChannelId, initialTeamId, initialChannelId);
|
||||
|
||||
const dt = Date.now();
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'appEntry');
|
||||
logInfo('ENTRY MODELS BATCHING TOOK', `${Date.now() - dt}ms`);
|
||||
setTeamLoading(serverUrl, false);
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ export const fetchAppEntryData = async (serverUrl: string, sinceArg: number, ini
|
||||
if (!initialTeamId && teamData.teams?.length && teamData.memberships?.length) {
|
||||
// If no initial team was set in the database but got teams in the response
|
||||
const config = await getConfig(database);
|
||||
const teamOrderPreference = getPreferenceValue(prefData.preferences || [], Preferences.TEAMS_ORDER, '', '') as string;
|
||||
const teamOrderPreference = getPreferenceValue<string>(prefData.preferences || [], Preferences.CATEGORIES.TEAMS_ORDER, '', '');
|
||||
const teamMembers = new Set(teamData.memberships.filter((m) => m.delete_at === 0).map((m) => m.team_id));
|
||||
const myTeams = teamData.teams!.filter((t) => teamMembers.has(t.id));
|
||||
const defaultTeam = selectDefaultTeam(myTeams, meData.user?.locale || DEFAULT_LOCALE, teamOrderPreference, config?.ExperimentalPrimaryTeam);
|
||||
@@ -431,7 +431,7 @@ const graphQLSyncAllChannelMembers = async (serverUrl: string) => {
|
||||
const modelPromises = await prepareMyChannelsForTeam(operator, '', channels, memberships, undefined, true);
|
||||
const models = (await Promise.all(modelPromises)).flat();
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'graphQLSyncAllChannelMembers');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ export async function deferredAppEntryGraphQLActions(
|
||||
modelPromises.push(operator.handleRole({roles, prepareRecordsOnly: true}));
|
||||
}
|
||||
const models = (await Promise.all(modelPromises)).flat();
|
||||
operator.batchRecords(models);
|
||||
operator.batchRecords(models, 'deferredAppEntryActions');
|
||||
|
||||
setTimeout(() => {
|
||||
if (result.chData?.channels?.length && result.chData.memberships?.length) {
|
||||
@@ -217,7 +217,7 @@ export const entryGQL = async (serverUrl: string, currentTeamId?: string, curren
|
||||
if (!teamData.teams.length) {
|
||||
initialTeamId = '';
|
||||
} else if (!initialTeamId || !teamData.teams.find((t) => t.id === currentTeamId && t.delete_at === 0)) {
|
||||
const teamOrderPreference = getPreferenceValue(prefData.preferences || [], Preferences.TEAMS_ORDER, '', '') as string;
|
||||
const teamOrderPreference = getPreferenceValue<string>(prefData.preferences || [], Preferences.CATEGORIES.TEAMS_ORDER, '', '');
|
||||
initialTeamId = selectDefaultTeam(teamData.teams, meData.user.locale, teamOrderPreference, config.ExperimentalPrimaryTeam)?.id || '';
|
||||
}
|
||||
const gqlRoles = [
|
||||
|
||||
@@ -68,7 +68,7 @@ export async function loginEntry({serverUrl, user, deviceToken}: AfterLoginArgs)
|
||||
setCurrentTeamAndChannelId(operator, initialTeamId, '');
|
||||
}
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'loginEntry');
|
||||
setTeamLoading(serverUrl, false);
|
||||
|
||||
const config = clData.config || {} as ClientConfig;
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import {fetchAndSwitchToThread} from '@actions/remote/thread';
|
||||
import {Preferences, Screens} from '@constants';
|
||||
import {Screens} from '@constants';
|
||||
import {getDefaultThemeByAppearance} from '@context/theme';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getMyChannel} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {queryThemePreferences} from '@queries/servers/preference';
|
||||
import {getConfig, getCurrentTeamId, getLicense, getWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
||||
import {getMyTeamById} from '@queries/servers/team';
|
||||
import {getIsCRTEnabled} from '@queries/servers/thread';
|
||||
@@ -53,7 +53,7 @@ export async function pushNotificationEntry(serverUrl: string, notification: Not
|
||||
// When opening the app from a push notification the theme may not be set in the EphemeralStore
|
||||
// causing the goToScreen to use the Appearance theme instead and that causes the screen background color to potentially
|
||||
// not match the theme
|
||||
const themes = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_THEME, teamId).fetch();
|
||||
const themes = await queryThemePreferences(database, teamId).fetch();
|
||||
let theme = getDefaultThemeByAppearance();
|
||||
if (themes.length) {
|
||||
theme = setThemeDefaults(JSON.parse(themes[0].value) as Theme);
|
||||
@@ -136,7 +136,7 @@ export async function pushNotificationEntry(serverUrl: string, notification: Not
|
||||
await NavigationStore.waitUntilScreenHasLoaded(Screens.THREAD);
|
||||
}
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'pushNotificationEntry');
|
||||
setTeamLoading(serverUrl, false);
|
||||
|
||||
const {id: currentUserId, locale: currentUserLocale} = (await getCurrentUser(operator.database))!;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import {DOWNLOAD_TIMEOUT} from '@constants/network';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
@@ -32,10 +33,11 @@ export const uploadFile = (
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
return {cancel: client.uploadPostAttachment(file, channelId, onProgress, onComplete, onError, skipBytes)};
|
||||
} catch (error) {
|
||||
logDebug('uploadFile', error);
|
||||
return {error: error as ClientError};
|
||||
}
|
||||
return {cancel: client.uploadPostAttachment(file, channelId, onProgress, onComplete, onError, skipBytes)};
|
||||
};
|
||||
|
||||
export const fetchPublicLink = async (serverUrl: string, fileId: string) => {
|
||||
|
||||
@@ -83,7 +83,7 @@ export const fetchGroupsForChannel = async (serverUrl: string, channelId: string
|
||||
]);
|
||||
|
||||
if (!fetchOnly) {
|
||||
await operator.batchRecords([...groups, ...groupChannels]);
|
||||
await operator.batchRecords([...groups, ...groupChannels], 'fetchGroupsForChannel');
|
||||
}
|
||||
|
||||
return {groups, groupChannels};
|
||||
@@ -110,7 +110,7 @@ export const fetchGroupsForTeam = async (serverUrl: string, teamId: string, fetc
|
||||
]);
|
||||
|
||||
if (!fetchOnly) {
|
||||
await operator.batchRecords([...groups, ...groupTeams]);
|
||||
await operator.batchRecords([...groups, ...groupTeams], 'fetchGroupsForTeam');
|
||||
}
|
||||
|
||||
return {groups, groupTeams};
|
||||
@@ -136,7 +136,7 @@ export const fetchGroupsForMember = async (serverUrl: string, userId: string, fe
|
||||
]);
|
||||
|
||||
if (!fetchOnly) {
|
||||
await operator.batchRecords([...groups, ...groupMemberships]);
|
||||
await operator.batchRecords([...groups, ...groupMemberships], 'fetchGroupsForMember');
|
||||
}
|
||||
|
||||
return {groups, groupMemberships};
|
||||
|
||||
@@ -128,7 +128,7 @@ export async function createPost(serverUrl: string, post: Partial<Post>, files:
|
||||
initialPostModels.push(...reactionModels);
|
||||
}
|
||||
|
||||
await operator.batchRecords(initialPostModels);
|
||||
await operator.batchRecords(initialPostModels, 'createPost - initial');
|
||||
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
|
||||
@@ -167,7 +167,7 @@ export async function createPost(serverUrl: string, post: Partial<Post>, files:
|
||||
models.push(...threadModels);
|
||||
}
|
||||
}
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'createPost - failure');
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
@@ -192,7 +192,7 @@ export async function createPost(serverUrl: string, post: Partial<Post>, files:
|
||||
models.push(...threadModels);
|
||||
}
|
||||
}
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'createPost - success');
|
||||
|
||||
newPost = created;
|
||||
|
||||
@@ -237,7 +237,7 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
|
||||
p.props = newPost.props;
|
||||
p.updateAt = timestamp;
|
||||
});
|
||||
await operator.batchRecords([post]);
|
||||
await operator.batchRecords([post], 'retryFailedPost - first update');
|
||||
|
||||
const created = await client.createPost(newPost);
|
||||
const models = await operator.handlePosts({
|
||||
@@ -253,7 +253,7 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
|
||||
models.push(member);
|
||||
}
|
||||
}
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'retryFailedPost - success update');
|
||||
} catch (error) {
|
||||
if (isServerError(error) && (
|
||||
error.server_error_id === ServerErrors.DELETED_ROOT_POST_ERROR ||
|
||||
@@ -268,7 +268,7 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
|
||||
failed: true,
|
||||
};
|
||||
});
|
||||
await operator.batchRecords([post]);
|
||||
await operator.batchRecords([post], 'retryFailedPost - error update');
|
||||
}
|
||||
|
||||
return {error};
|
||||
@@ -375,7 +375,7 @@ export async function fetchPosts(serverUrl: string, channelId: string, page = 0,
|
||||
models.push(...threadModels);
|
||||
}
|
||||
}
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'fetchPosts');
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
@@ -434,7 +434,7 @@ export async function fetchPostsBefore(serverUrl: string, channelId: string, pos
|
||||
}
|
||||
}
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'fetchPostsBefore');
|
||||
} catch (error) {
|
||||
logError('FETCH POSTS BEFORE ERROR', error);
|
||||
}
|
||||
@@ -489,7 +489,7 @@ export async function fetchPostsSince(serverUrl: string, channelId: string, sinc
|
||||
models.push(...threadModels);
|
||||
}
|
||||
}
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'fetchPostsSince');
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
@@ -621,7 +621,7 @@ export async function fetchPostThread(serverUrl: string, postId: string, options
|
||||
models.push(...threadModels);
|
||||
}
|
||||
}
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'fetchPostThread');
|
||||
}
|
||||
setFetchingThreadState(postId, false);
|
||||
return {posts: extractRecordsForTable<PostModel>(posts, MM_TABLES.SERVER.POST)};
|
||||
@@ -697,7 +697,7 @@ export async function fetchPostsAround(serverUrl: string, channelId: string, pos
|
||||
models.push(...threadModels);
|
||||
}
|
||||
}
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'fetchPostsAround');
|
||||
}
|
||||
|
||||
return {posts: extractRecordsForTable<PostModel>(posts, MM_TABLES.SERVER.POST)};
|
||||
@@ -751,7 +751,7 @@ export async function fetchMissingChannelsFromPosts(serverUrl: string, posts: Po
|
||||
return mdls;
|
||||
});
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'fetchMissingChannelsFromPosts');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -809,7 +809,7 @@ export async function fetchPostById(serverUrl: string, postId: string, fetchOnly
|
||||
}
|
||||
}
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'fetchPostById');
|
||||
}
|
||||
|
||||
return {post};
|
||||
@@ -1036,7 +1036,7 @@ export async function fetchSavedPosts(serverUrl: string, teamId?: string, channe
|
||||
return mdls;
|
||||
});
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'fetchSavedPosts');
|
||||
|
||||
return {
|
||||
order,
|
||||
@@ -1118,7 +1118,7 @@ export async function fetchPinnedPosts(serverUrl: string, channelId: string) {
|
||||
return mdls;
|
||||
});
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'fetchPinnedPosts');
|
||||
|
||||
return {
|
||||
order,
|
||||
|
||||
@@ -5,14 +5,12 @@ import {General, Preferences} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getChannelById} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {querySavedPostsPreferences} from '@queries/servers/preference';
|
||||
import {getCurrentUserId} from '@queries/servers/system';
|
||||
import {getUserIdFromChannelName} from '@utils/user';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
const {CATEGORY_DIRECT_CHANNEL_SHOW, CATEGORY_GROUP_CHANNEL_SHOW, CATEGORY_FAVORITE_CHANNEL, CATEGORY_SAVED_POST} = Preferences;
|
||||
|
||||
export type MyPreferencesRequest = {
|
||||
preferences?: PreferenceType[];
|
||||
error?: unknown;
|
||||
@@ -56,7 +54,7 @@ export const saveFavoriteChannel = async (serverUrl: string, channelId: string,
|
||||
try {
|
||||
const userId = await getCurrentUserId(operator.database);
|
||||
const favPref: PreferenceType = {
|
||||
category: CATEGORY_FAVORITE_CHANNEL,
|
||||
category: Preferences.CATEGORIES.FAVORITE_CHANNEL,
|
||||
name: channelId,
|
||||
user_id: userId,
|
||||
value: String(isFavorite),
|
||||
@@ -77,7 +75,7 @@ export const savePostPreference = async (serverUrl: string, postId: string) => {
|
||||
const userId = await getCurrentUserId(operator.database);
|
||||
const pref: PreferenceType = {
|
||||
user_id: userId,
|
||||
category: CATEGORY_SAVED_POST,
|
||||
category: Preferences.CATEGORIES.SAVED_POST,
|
||||
name: postId,
|
||||
value: 'true',
|
||||
};
|
||||
@@ -116,23 +114,15 @@ export const savePreference = async (serverUrl: string, preferences: PreferenceT
|
||||
};
|
||||
|
||||
export const deleteSavedPost = async (serverUrl: string, postId: string) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
try {
|
||||
const userId = await getCurrentUserId(operator.database);
|
||||
const records = await queryPreferencesByCategoryAndName(operator.database, CATEGORY_SAVED_POST, postId).fetch();
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const userId = await getCurrentUserId(database);
|
||||
const records = await querySavedPostsPreferences(database, postId).fetch();
|
||||
const postPreferenceRecord = records.find((r) => postId === r.name);
|
||||
const pref = {
|
||||
user_id: userId,
|
||||
category: CATEGORY_SAVED_POST,
|
||||
category: Preferences.CATEGORIES.SAVED_POST,
|
||||
name: postId,
|
||||
value: 'true',
|
||||
};
|
||||
@@ -161,7 +151,8 @@ export const setDirectChannelVisible = async (serverUrl: string, channelId: stri
|
||||
const channel = await getChannelById(database, channelId);
|
||||
if (channel?.type === General.DM_CHANNEL || channel?.type === General.GM_CHANNEL) {
|
||||
const userId = await getCurrentUserId(database);
|
||||
const category = channel.type === General.DM_CHANNEL ? CATEGORY_DIRECT_CHANNEL_SHOW : CATEGORY_GROUP_CHANNEL_SHOW;
|
||||
const {DIRECT_CHANNEL_SHOW, GROUP_CHANNEL_SHOW} = Preferences.CATEGORIES;
|
||||
const category = channel.type === General.DM_CHANNEL ? DIRECT_CHANNEL_SHOW : GROUP_CHANNEL_SHOW;
|
||||
const name = channel.type === General.DM_CHANNEL ? getUserIdFromChannelName(userId, channel.name) : channelId;
|
||||
const pref: PreferenceType = {
|
||||
user_id: userId,
|
||||
@@ -185,7 +176,7 @@ export const savePreferredSkinTone = async (serverUrl: string, skinCode: string)
|
||||
const userId = await getCurrentUserId(database);
|
||||
const pref: PreferenceType = {
|
||||
user_id: userId,
|
||||
category: Preferences.CATEGORY_EMOJI,
|
||||
category: Preferences.CATEGORIES.EMOJI,
|
||||
name: Preferences.EMOJI_SKINTONE,
|
||||
value: skinCode,
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function addReaction(serverUrl: string, postId: string, emojiName:
|
||||
models.push(...recent);
|
||||
}
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'addReaction');
|
||||
|
||||
return {reaction};
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {selectDefaultTeam} from '@helpers/api/team';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {prepareCategoriesAndCategoriesChannels} from '@queries/servers/categories';
|
||||
import {prepareMyChannelsForTeam} from '@queries/servers/channel';
|
||||
import {prepareMyPreferences, queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {prepareMyPreferences, queryDisplayNamePreferences} from '@queries/servers/preference';
|
||||
import {prepareCommonSystemValues, getConfig, getLicense} from '@queries/servers/system';
|
||||
import {prepareMyTeams} from '@queries/servers/team';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
@@ -60,7 +60,7 @@ export async function retryInitialTeamAndChannel(serverUrl: string) {
|
||||
|
||||
// select initial team
|
||||
if (!clData.error && !prefData.error && !teamData.error) {
|
||||
const teamOrderPreference = getPreferenceValue(prefData.preferences!, Preferences.TEAMS_ORDER, '', '') as string;
|
||||
const teamOrderPreference = getPreferenceValue<string>(prefData.preferences!, Preferences.CATEGORIES.TEAMS_ORDER, '', '');
|
||||
const teamRoles: string[] = [];
|
||||
const teamMembers = new Set<string>();
|
||||
|
||||
@@ -117,7 +117,7 @@ export async function retryInitialTeamAndChannel(serverUrl: string) {
|
||||
),
|
||||
])).flat();
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'retryInitialTeamAndChannel');
|
||||
|
||||
const directChannels = chData!.channels!.filter(isDMorGM);
|
||||
const channelsToFetchProfiles = new Set<Channel>(directChannels);
|
||||
@@ -157,7 +157,7 @@ export async function retryInitialChannel(serverUrl: string, teamId: string) {
|
||||
return {error: true};
|
||||
}
|
||||
|
||||
const prefs = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
|
||||
const prefs = await queryDisplayNamePreferences(database, Preferences.NAME_NAME_FORMAT).fetch();
|
||||
const preferences: PreferenceType[] = prefs.map((p) => ({
|
||||
category: p.category,
|
||||
name: p.name,
|
||||
@@ -196,7 +196,7 @@ export async function retryInitialChannel(serverUrl: string, teamId: string) {
|
||||
prepareCommonSystemValues(operator, {currentChannelId: initialChannel?.id}),
|
||||
])).flat();
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'retryInitialChannel');
|
||||
|
||||
const directChannels = chData!.channels!.filter(isDMorGM);
|
||||
const channelsToFetchProfiles = new Set<Channel>(directChannels);
|
||||
|
||||
@@ -103,7 +103,7 @@ export const searchPosts = async (serverUrl: string, teamId: string, params: Pos
|
||||
return mdls;
|
||||
});
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'searchPosts');
|
||||
return {
|
||||
order,
|
||||
posts: postsArray,
|
||||
|
||||
@@ -86,7 +86,7 @@ export async function addUserToTeam(serverUrl: string, teamId: string, userId: s
|
||||
prepareCategoriesAndCategoriesChannels(operator, categories || [], true),
|
||||
])).flat();
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'addUserToTeam');
|
||||
setTeamLoading(serverUrl, false);
|
||||
loadEventSent = false;
|
||||
|
||||
@@ -199,7 +199,7 @@ export async function fetchMyTeams(serverUrl: string, fetchOnly = false): Promis
|
||||
const models = await Promise.all(modelPromises);
|
||||
const flattenedModels = models.flat();
|
||||
if (flattenedModels.length > 0) {
|
||||
await operator.batchRecords(flattenedModels);
|
||||
await operator.batchRecords(flattenedModels, 'fetchMyTeams');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,7 +233,7 @@ export async function fetchMyTeam(serverUrl: string, teamId: string, fetchOnly =
|
||||
const models = await Promise.all(modelPromises);
|
||||
const flattenedModels = models.flat();
|
||||
if (flattenedModels?.length > 0) {
|
||||
await operator.batchRecords(flattenedModels);
|
||||
await operator.batchRecords(flattenedModels, 'fetchMyTeam');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,7 +362,7 @@ export async function fetchTeamByName(serverUrl: string, teamName: string, fetch
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (operator) {
|
||||
const models = await operator.handleTeam({teams: [team], prepareRecordsOnly: true});
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'fetchTeamByName');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,7 +441,7 @@ export async function handleTeamChange(serverUrl: string, teamId: string) {
|
||||
}
|
||||
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'handleTeamChange');
|
||||
}
|
||||
DeviceEventEmitter.emit(Events.TEAM_SWITCH, false);
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export async function updateTermsOfServiceStatus(serverUrl: string, id: string,
|
||||
u.termsOfServiceId = '';
|
||||
}
|
||||
});
|
||||
operator.batchRecords([currentUser]);
|
||||
operator.batchRecords([currentUser], 'updateTermsOfServiceStatus');
|
||||
}
|
||||
return {resp};
|
||||
} catch (error) {
|
||||
|
||||
@@ -360,7 +360,7 @@ export const syncTeamThreads = async (serverUrl: string, teamId: string, prepare
|
||||
const allNewThreads = await fetchThreads(
|
||||
serverUrl,
|
||||
teamId,
|
||||
{deleted: true, since: syncData.latest},
|
||||
{deleted: true, since: syncData.latest + 1},
|
||||
);
|
||||
if (allNewThreads.error) {
|
||||
return {error: allNewThreads.error};
|
||||
@@ -395,7 +395,7 @@ export const syncTeamThreads = async (serverUrl: string, teamId: string, prepare
|
||||
|
||||
if (!prepareRecordsOnly && models?.length) {
|
||||
try {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'syncTeamThreads');
|
||||
} catch (err) {
|
||||
if (__DEV__) {
|
||||
throw err;
|
||||
@@ -460,7 +460,7 @@ export const loadEarlierThreads = async (serverUrl: string, teamId: string, last
|
||||
|
||||
if (!prepareRecordsOnly && models?.length) {
|
||||
try {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'loadEarlierThreads');
|
||||
} catch (err) {
|
||||
if (__DEV__) {
|
||||
throw err;
|
||||
|
||||
@@ -74,7 +74,7 @@ export const fetchMe = async (serverUrl: string, fetchOnly = false): Promise<MyU
|
||||
}
|
||||
};
|
||||
|
||||
export async function fetchProfilesInChannel(serverUrl: string, channelId: string, excludeUserId?: string, fetchOnly = false): Promise<ProfilesInChannelRequest> {
|
||||
export async function fetchProfilesInChannel(serverUrl: string, channelId: string, excludeUserId?: string, options?: GetUsersOptions, fetchOnly = false): Promise<ProfilesInChannelRequest> {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
@@ -83,7 +83,7 @@ export async function fetchProfilesInChannel(serverUrl: string, channelId: strin
|
||||
}
|
||||
|
||||
try {
|
||||
const users = await client.getProfilesInChannel(channelId);
|
||||
const users = await client.getProfilesInChannel(channelId, options);
|
||||
const uniqueUsers = Array.from(new Set(users));
|
||||
const filteredUsers = uniqueUsers.filter((u) => u.id !== excludeUserId);
|
||||
if (!fetchOnly) {
|
||||
@@ -102,12 +102,13 @@ export async function fetchProfilesInChannel(serverUrl: string, channelId: strin
|
||||
modelPromises.push(prepare);
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
await operator.batchRecords(models.flat());
|
||||
await operator.batchRecords(models.flat(), 'fetchProfilesInChannel');
|
||||
}
|
||||
}
|
||||
|
||||
return {channelId, users: filteredUsers};
|
||||
} catch (error) {
|
||||
logError('fetchProfilesInChannel', error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {channelId, error};
|
||||
}
|
||||
@@ -177,7 +178,7 @@ export async function fetchProfilesInGroupChannels(serverUrl: string, groupChann
|
||||
}
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
await operator.batchRecords(models.flat());
|
||||
await operator.batchRecords(models.flat(), 'fetchProfilesInGroupChannels');
|
||||
}
|
||||
|
||||
return {data};
|
||||
@@ -198,7 +199,7 @@ export async function fetchProfilesPerChannels(serverUrl: string, channelIds: st
|
||||
const data: ProfilesInChannelRequest[] = [];
|
||||
|
||||
for await (const cIds of channels) {
|
||||
const requests = cIds.map((id) => fetchProfilesInChannel(serverUrl, id, excludeUserId, true));
|
||||
const requests = cIds.map((id) => fetchProfilesInChannel(serverUrl, id, excludeUserId, undefined, true));
|
||||
const response = await Promise.all(requests);
|
||||
data.push(...response);
|
||||
}
|
||||
@@ -229,7 +230,7 @@ export async function fetchProfilesPerChannels(serverUrl: string, channelIds: st
|
||||
}
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
await operator.batchRecords(models.flat());
|
||||
await operator.batchRecords(models.flat(), 'fetchProfilesPerChannels');
|
||||
}
|
||||
|
||||
return {data};
|
||||
@@ -365,7 +366,7 @@ export async function fetchStatusByIds(serverUrl: string, userIds: string[], fet
|
||||
user.prepareStatus(status?.status || General.OFFLINE);
|
||||
}
|
||||
|
||||
await operator.batchRecords(users);
|
||||
await operator.batchRecords(users, 'fetchStatusByIds');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,7 +531,7 @@ export const fetchProfilesInTeam = async (serverUrl: string, teamId: string, pag
|
||||
}
|
||||
};
|
||||
|
||||
export const searchProfiles = async (serverUrl: string, term: string, options: any = {}, fetchOnly = false) => {
|
||||
export const searchProfiles = async (serverUrl: string, term: string, options: SearchUserOptions, fetchOnly = false) => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
@@ -563,6 +564,7 @@ export const searchProfiles = async (serverUrl: string, term: string, options: a
|
||||
|
||||
return {data: users};
|
||||
} catch (error) {
|
||||
logError('searchProfiles', error);
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
@@ -639,7 +641,7 @@ export async function updateAllUsersSince(serverUrl: string, since: number, fetc
|
||||
modelsToBatch.push(...models);
|
||||
}
|
||||
|
||||
await operator.batchRecords(modelsToBatch);
|
||||
await operator.batchRecords(modelsToBatch, 'updateAllUsersSince');
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
@@ -675,7 +677,7 @@ export async function updateUsersNoLongerVisible(serverUrl: string, prepareRecor
|
||||
}
|
||||
}
|
||||
if (models.length && !prepareRecordsOnly) {
|
||||
serverDatabase.operator.batchRecords(models);
|
||||
serverDatabase.operator.batchRecords(models, 'updateUsersNoLongerVisible');
|
||||
}
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
@@ -915,7 +917,7 @@ export const fetchTeamAndChannelMembership = async (serverUrl: string, userId: s
|
||||
}
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
await operator.batchRecords(models.flat());
|
||||
await operator.batchRecords(models.flat(), 'fetchTeamAndChannelMembership');
|
||||
return {error: undefined};
|
||||
} catch (error) {
|
||||
return {error};
|
||||
|
||||
@@ -96,7 +96,7 @@ export async function handleCategoryOrderUpdatedEvent(serverUrl: string, msg: We
|
||||
c.sortOrder = order.findIndex(findOrder);
|
||||
});
|
||||
});
|
||||
await operator.batchRecords(categories);
|
||||
await operator.batchRecords(categories, 'handleCategoryOrderUpdatedEvent');
|
||||
}
|
||||
} catch (e) {
|
||||
logError('Category WS: handleCategoryOrderUpdatedEvent', e, msg);
|
||||
|
||||
@@ -57,7 +57,7 @@ export async function handleChannelCreatedEvent(serverUrl: string, msg: any) {
|
||||
}
|
||||
}
|
||||
}
|
||||
operator.batchRecords(models);
|
||||
operator.batchRecords(models, 'handleChannelCreatedEvent');
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
@@ -109,7 +109,7 @@ export async function handleChannelUpdatedEvent(serverUrl: string, msg: any) {
|
||||
if (infoModel.model) {
|
||||
models.push(...infoModel.model);
|
||||
}
|
||||
operator.batchRecords(models);
|
||||
operator.batchRecords(models, 'handleChannelUpdatedEvent');
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
@@ -165,7 +165,7 @@ export async function handleChannelMemberUpdatedEvent(serverUrl: string, msg: an
|
||||
if (rolesRequest.roles?.length) {
|
||||
models.push(...await operator.handleRole({roles: rolesRequest.roles, prepareRecordsOnly: true}));
|
||||
}
|
||||
operator.batchRecords(models);
|
||||
operator.batchRecords(models, 'handleChannelMemberUpdatedEvent');
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
@@ -235,7 +235,7 @@ export async function handleDirectAddedEvent(serverUrl: string, msg: WebSocketMe
|
||||
models.push(...userModels);
|
||||
}
|
||||
|
||||
operator.batchRecords(models);
|
||||
operator.batchRecords(models, 'handleDirectAddedEvent');
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
@@ -267,7 +267,7 @@ export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any)
|
||||
const prepareModels = await Promise.all(prepare);
|
||||
const flattenedModels = prepareModels.flat();
|
||||
if (flattenedModels?.length > 0) {
|
||||
await operator.batchRecords(flattenedModels);
|
||||
await operator.batchRecords(flattenedModels, 'handleUserAddedToChannelEvent - prepareMyChannelsForTeam');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any)
|
||||
}
|
||||
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'handleUserAddedToChannelEvent');
|
||||
}
|
||||
|
||||
await fetchChannelStats(serverUrl, channelId, false);
|
||||
@@ -356,7 +356,7 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
|
||||
}
|
||||
}
|
||||
|
||||
operator.batchRecords(models);
|
||||
operator.batchRecords(models, 'handleUserRemovedFromChannelEvent');
|
||||
} catch (error) {
|
||||
logDebug('cannot handle user removed from channel websocket event', error);
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ async function doReconnect(serverUrl: string) {
|
||||
await handleEntryAfterLoadNavigation(serverUrl, teamData.memberships || [], chData?.memberships || [], currentTeam?.id || '', currentChannel?.id || '', initialTeamId, initialChannelId);
|
||||
|
||||
const dt = Date.now();
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'doReconnect');
|
||||
logInfo('WEBSOCKET RECONNECT MODELS BATCHING TOOK', `${Date.now() - dt}ms`);
|
||||
setTeamLoading(serverUrl, false);
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessag
|
||||
|
||||
models.push(...postModels);
|
||||
|
||||
operator.batchRecords(models);
|
||||
operator.batchRecords(models, 'handleNewPostEvent');
|
||||
}
|
||||
|
||||
export async function handlePostEdited(serverUrl: string, msg: WebSocketMessage) {
|
||||
@@ -220,7 +220,7 @@ export async function handlePostEdited(serverUrl: string, msg: WebSocketMessage)
|
||||
});
|
||||
models.push(...postModels);
|
||||
|
||||
operator.batchRecords(models);
|
||||
operator.batchRecords(models, 'handlePostEdited');
|
||||
}
|
||||
|
||||
export async function handlePostDeleted(serverUrl: string, msg: WebSocketMessage) {
|
||||
@@ -263,7 +263,7 @@ export async function handlePostDeleted(serverUrl: string, msg: WebSocketMessage
|
||||
}
|
||||
|
||||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'handlePostDeleted');
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
|
||||
@@ -107,7 +107,7 @@ async function handleSavePostAdded(serverUrl: string, preferences: PreferenceTyp
|
||||
return;
|
||||
}
|
||||
|
||||
const savedPosts = preferences.filter((p) => p.category === Preferences.CATEGORY_SAVED_POST);
|
||||
const savedPosts = preferences.filter((p) => p.category === Preferences.CATEGORIES.SAVED_POST);
|
||||
for await (const saved of savedPosts) {
|
||||
const post = await getPostById(database, saved.name);
|
||||
if (!post) {
|
||||
|
||||
@@ -66,7 +66,7 @@ export async function handleUserRoleUpdatedEvent(serverUrl: string, msg: WebSock
|
||||
models.push(user);
|
||||
}
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'handleUserRoleUpdatedEvent');
|
||||
}
|
||||
|
||||
export async function handleTeamMemberRoleUpdatedEvent(serverUrl: string, msg: WebSocketMessage): Promise<void> {
|
||||
@@ -118,7 +118,7 @@ export async function handleTeamMemberRoleUpdatedEvent(serverUrl: string, msg: W
|
||||
});
|
||||
models.push(...teamMembership);
|
||||
|
||||
await operator.batchRecords(models);
|
||||
await operator.batchRecords(models, 'handleTeamMemberRoleUpdatedEvent');
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@@ -162,5 +162,5 @@ const fetchAndStoreJoinedTeamInfo = async (serverUrl: string, operator: ServerDa
|
||||
}
|
||||
|
||||
const models = await Promise.all(modelPromises);
|
||||
await operator.batchRecords(models.flat());
|
||||
await operator.batchRecords(models.flat(), 'fetchAndStoreJoinedTeamInfo');
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import DatabaseManager from '@database/manager';
|
||||
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
|
||||
import WebsocketManager from '@managers/websocket_manager';
|
||||
import {queryChannelsByTypes, queryUserChannelsByTypes} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {queryDisplayNamePreferences} from '@queries/servers/preference';
|
||||
import {getConfig, getLicense} from '@queries/servers/system';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import {displayUsername} from '@utils/user';
|
||||
@@ -71,7 +71,7 @@ export async function handleUserUpdatedEvent(serverUrl: string, msg: WebSocketMe
|
||||
modelsToBatch.push(...userModel);
|
||||
|
||||
try {
|
||||
await operator.batchRecords(modelsToBatch);
|
||||
await operator.batchRecords(modelsToBatch, 'handleUserUpdatedEvent');
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export async function handleUserTypingEvent(serverUrl: string, msg: WebSocketMes
|
||||
const {users, existingUsers} = await fetchUsersByIds(serverUrl, [msg.data.user_id]);
|
||||
const user = users?.[0] || existingUsers?.[0];
|
||||
|
||||
const namePreference = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
|
||||
const namePreference = await queryDisplayNamePreferences(database, Preferences.NAME_NAME_FORMAT).fetch();
|
||||
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(namePreference, config.LockTeammateNameDisplay, config.TeammateNameDisplay, license);
|
||||
const currentUser = await getCurrentUser(database);
|
||||
const username = displayUsername(user, currentUser?.locale, teammateDisplayNameSetting);
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
|
||||
import {buildQueryString} from '@utils/helpers';
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientAppsMix {
|
||||
executeAppCall: <Res = unknown>(call: AppCallRequest, trackAsSubmit: boolean) => Promise<AppCallResponse<Res>>;
|
||||
getAppsBindings: (userID: string, channelID: string, teamID: string) => Promise<AppBinding[]>;
|
||||
}
|
||||
|
||||
const ClientApps = (superclass: any) => class extends superclass {
|
||||
executeAppCall = async <Res = unknown>(call: AppCallRequest, trackAsSubmit: boolean): Promise<AppCallResponse<Res>> => {
|
||||
const ClientApps = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
executeAppCall = async (call: AppCallRequest, trackAsSubmit: boolean) => {
|
||||
const callCopy = {
|
||||
...call,
|
||||
context: {
|
||||
|
||||
@@ -26,6 +26,7 @@ export default class ClientBase {
|
||||
requestHeaders: {[x: string]: string} = {};
|
||||
serverVersion = '';
|
||||
urlVersion = '/api/v4';
|
||||
enableLogging = false;
|
||||
|
||||
constructor(apiClient: APIClientInterface, serverUrl: string, bearerToken?: string, csrfToken?: string) {
|
||||
this.apiClient = apiClient;
|
||||
@@ -45,6 +46,10 @@ export default class ClientBase {
|
||||
}
|
||||
}
|
||||
|
||||
getBaseRoute() {
|
||||
return this.apiClient.baseUrl || '';
|
||||
}
|
||||
|
||||
getAbsoluteUrl(baseUrl?: string) {
|
||||
if (typeof baseUrl !== 'string' || !baseUrl.startsWith('/')) {
|
||||
return baseUrl;
|
||||
@@ -303,7 +308,7 @@ export default class ClientBase {
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
return returnDataOnly ? response.data : response;
|
||||
return returnDataOnly ? (response.data || {}) : response;
|
||||
}
|
||||
|
||||
throw new ClientError(this.apiClient.baseUrl, {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientCategoriesMix {
|
||||
getCategories: (userId: string, teamId: string) => Promise<CategoriesWithOrder>;
|
||||
getCategoriesOrder: (userId: string, teamId: string) => Promise<string[]>;
|
||||
@@ -8,7 +10,7 @@ export interface ClientCategoriesMix {
|
||||
updateChannelCategories: (userId: string, teamId: string, categories: CategoryWithChannels[]) => Promise<CategoriesWithOrder>;
|
||||
}
|
||||
|
||||
const ClientCategories = (superclass: any) => class extends superclass {
|
||||
const ClientCategories = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
getCategories = async (userId: string, teamId: string) => {
|
||||
return this.doFetch(
|
||||
`${this.getCategoriesRoute(userId, teamId)}`,
|
||||
|
||||
@@ -5,6 +5,8 @@ import {buildQueryString} from '@utils/helpers';
|
||||
|
||||
import {PER_PAGE_DEFAULT} from './constants';
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientChannelsMix {
|
||||
getAllChannels: (page?: number, perPage?: number, notAssociatedToGroup?: string, excludeDefaultChannels?: boolean, includeTotalCount?: boolean) => Promise<any>;
|
||||
createChannel: (channel: Channel) => Promise<Channel>;
|
||||
@@ -40,9 +42,11 @@ export interface ClientChannelsMix {
|
||||
searchChannels: (teamId: string, term: string) => Promise<Channel[]>;
|
||||
searchArchivedChannels: (teamId: string, term: string) => Promise<Channel[]>;
|
||||
searchAllChannels: (term: string, teamIds: string[], archivedOnly?: boolean) => Promise<Channel[]>;
|
||||
updateChannelMemberSchemeRoles: (channelId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean) => Promise<any>;
|
||||
getMemberInChannel: (channelId: string, userId: string) => Promise<any>;
|
||||
}
|
||||
|
||||
const ClientChannels = (superclass: any) => class extends superclass {
|
||||
const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
getAllChannels = async (page = 0, perPage = PER_PAGE_DEFAULT, notAssociatedToGroup = '', excludeDefaultChannels = false, includeTotalCount = false) => {
|
||||
const queryData = {
|
||||
page,
|
||||
@@ -58,7 +62,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
createChannel = async (channel: Channel) => {
|
||||
this.analytics.trackAPI('api_channels_create', {team_id: channel.team_id});
|
||||
this.analytics?.trackAPI('api_channels_create', {team_id: channel.team_id});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelsRoute()}`,
|
||||
@@ -67,7 +71,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
createDirectChannel = async (userIds: string[]) => {
|
||||
this.analytics.trackAPI('api_channels_create_direct');
|
||||
this.analytics?.trackAPI('api_channels_create_direct');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelsRoute()}/direct`,
|
||||
@@ -76,7 +80,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
createGroupChannel = async (userIds: string[]) => {
|
||||
this.analytics.trackAPI('api_channels_create_group');
|
||||
this.analytics?.trackAPI('api_channels_create_group');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelsRoute()}/group`,
|
||||
@@ -85,7 +89,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
deleteChannel = async (channelId: string) => {
|
||||
this.analytics.trackAPI('api_channels_delete', {channel_id: channelId});
|
||||
this.analytics?.trackAPI('api_channels_delete', {channel_id: channelId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}`,
|
||||
@@ -94,7 +98,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
unarchiveChannel = async (channelId: string) => {
|
||||
this.analytics.trackAPI('api_channels_unarchive', {channel_id: channelId});
|
||||
this.analytics?.trackAPI('api_channels_unarchive', {channel_id: channelId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}/restore`,
|
||||
@@ -103,7 +107,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
updateChannel = async (channel: Channel) => {
|
||||
this.analytics.trackAPI('api_channels_update', {channel_id: channel.id});
|
||||
this.analytics?.trackAPI('api_channels_update', {channel_id: channel.id});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channel.id)}`,
|
||||
@@ -116,7 +120,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
updateChannelPrivacy = async (channelId: string, privacy: any) => {
|
||||
this.analytics.trackAPI('api_channels_update_privacy', {channel_id: channelId, privacy});
|
||||
this.analytics?.trackAPI('api_channels_update_privacy', {channel_id: channelId, privacy});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}/privacy`,
|
||||
@@ -125,7 +129,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
patchChannel = async (channelId: string, channelPatch: Partial<Channel>) => {
|
||||
this.analytics.trackAPI('api_channels_patch', {channel_id: channelId});
|
||||
this.analytics?.trackAPI('api_channels_patch', {channel_id: channelId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}/patch`,
|
||||
@@ -134,7 +138,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
updateChannelNotifyProps = async (props: ChannelNotifyProps & {channel_id: string; user_id: string}) => {
|
||||
this.analytics.trackAPI('api_users_update_channel_notifications', {channel_id: props.channel_id});
|
||||
this.analytics?.trackAPI('api_users_update_channel_notifications', {channel_id: props.channel_id});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelMemberRoute(props.channel_id, props.user_id)}/notify_props`,
|
||||
@@ -143,7 +147,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getChannel = async (channelId: string) => {
|
||||
this.analytics.trackAPI('api_channel_get', {channel_id: channelId});
|
||||
this.analytics?.trackAPI('api_channel_get', {channel_id: channelId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}`,
|
||||
@@ -159,7 +163,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getChannelByNameAndTeamName = async (teamName: string, channelName: string, includeDeleted = false) => {
|
||||
this.analytics.trackAPI('api_channel_get_by_name_and_teamName', {channel_name: channelName, team_name: teamName, include_deleted: includeDeleted});
|
||||
this.analytics?.trackAPI('api_channel_get_by_name_and_teamName', {channel_name: channelName, team_name: teamName, include_deleted: includeDeleted});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getTeamNameRoute(teamName)}/channels/name/${channelName}?include_deleted=${includeDeleted}`,
|
||||
@@ -241,7 +245,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
addToChannel = async (userId: string, channelId: string, postRootId = '') => {
|
||||
this.analytics.trackAPI('api_channels_add_member', {channel_id: channelId});
|
||||
this.analytics?.trackAPI('api_channels_add_member', {channel_id: channelId});
|
||||
|
||||
const member = {user_id: userId, channel_id: channelId, post_root_id: postRootId};
|
||||
return this.doFetch(
|
||||
@@ -251,7 +255,7 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
removeFromChannel = async (userId: string, channelId: string) => {
|
||||
this.analytics.trackAPI('api_channels_remove_member', {channel_id: channelId});
|
||||
this.analytics?.trackAPI('api_channels_remove_member', {channel_id: channelId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelMemberRoute(channelId, userId)}`,
|
||||
@@ -327,6 +331,25 @@ const ClientChannels = (superclass: any) => class extends superclass {
|
||||
{method: 'post', body},
|
||||
);
|
||||
};
|
||||
|
||||
// Update a channel member's scheme_admin/scheme_user properties. Typically
|
||||
// this should either be scheme_admin=false, scheme_user=true for ordinary
|
||||
// channel member, or scheme_admin=true, scheme_user=true for a channel
|
||||
// admin.
|
||||
updateChannelMemberSchemeRoles = (channelId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean) => {
|
||||
const body = {scheme_user: isSchemeUser, scheme_admin: isSchemeAdmin};
|
||||
return this.doFetch(
|
||||
`${this.getChannelMembersRoute(channelId)}/${userId}/schemeRoles`,
|
||||
{method: 'put', body},
|
||||
);
|
||||
};
|
||||
|
||||
getMemberInChannel = (channelId: string, userId: string) => {
|
||||
return this.doFetch(
|
||||
`${this.getChannelMembersRoute(channelId)}/${userId}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ClientChannels;
|
||||
|
||||
@@ -5,6 +5,8 @@ import {buildQueryString} from '@utils/helpers';
|
||||
|
||||
import {PER_PAGE_DEFAULT} from './constants';
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientEmojisMix {
|
||||
getCustomEmoji: (id: string) => Promise<CustomEmoji>;
|
||||
getCustomEmojiByName: (name: string) => Promise<CustomEmoji>;
|
||||
@@ -15,7 +17,7 @@ export interface ClientEmojisMix {
|
||||
autocompleteCustomEmoji: (name: string) => Promise<CustomEmoji[]>;
|
||||
}
|
||||
|
||||
const ClientEmojis = (superclass: any) => class extends superclass {
|
||||
const ClientEmojis = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
getCustomEmoji = async (id: string) => {
|
||||
return this.doFetch(
|
||||
`${this.getEmojisRoute()}/${id}`,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import {toMilliseconds} from '@utils/datetime';
|
||||
|
||||
import type ClientBase from './base';
|
||||
import type {ClientResponse, ClientResponseError, ProgressPromise, UploadRequestOptions} from '@mattermost/react-native-network-client';
|
||||
|
||||
export interface ClientFilesMix {
|
||||
@@ -22,7 +23,7 @@ export interface ClientFilesMix {
|
||||
searchFilesWithParams: (teamId: string, FileSearchParams: string) => Promise<FileSearchRequest>;
|
||||
}
|
||||
|
||||
const ClientFiles = (superclass: any) => class extends superclass {
|
||||
const ClientFiles = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
getFileUrl(fileId: string, timestamp: number) {
|
||||
let url = `${this.apiClient.baseUrl}${this.getFileRoute(fileId)}`;
|
||||
if (timestamp) {
|
||||
@@ -76,13 +77,17 @@ const ClientFiles = (superclass: any) => class extends superclass {
|
||||
},
|
||||
timeoutInterval: toMilliseconds({minutes: 3}),
|
||||
};
|
||||
if (!file.localPath) {
|
||||
throw new Error('file does not have local path defined');
|
||||
}
|
||||
|
||||
const promise = this.apiClient.upload(url, file.localPath, options) as ProgressPromise<ClientResponse>;
|
||||
promise.progress!(onProgress).then(onComplete).catch(onError);
|
||||
return promise.cancel!;
|
||||
};
|
||||
|
||||
searchFilesWithParams = async (teamId: string, params: FileSearchParams) => {
|
||||
this.analytics.trackAPI('api_files_search');
|
||||
this.analytics?.trackAPI('api_files_search');
|
||||
const endpoint = teamId ? `${this.getTeamRoute(teamId)}/files/search` : `${this.getFilesRoute()}/search`;
|
||||
return this.doFetch(endpoint, {method: 'post', body: params});
|
||||
};
|
||||
|
||||
@@ -6,6 +6,8 @@ import {buildQueryString} from '@utils/helpers';
|
||||
import {PER_PAGE_DEFAULT} from './constants';
|
||||
import ClientError from './error';
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
type PoliciesResponse<T> = {
|
||||
policies: T[];
|
||||
total_count: number;
|
||||
@@ -25,7 +27,7 @@ export interface ClientGeneralMix {
|
||||
getRedirectLocation: (urlParam: string) => Promise<Record<string, string>>;
|
||||
}
|
||||
|
||||
const ClientGeneral = (superclass: any) => class extends superclass {
|
||||
const ClientGeneral = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
getOpenGraphMetadata = async (url: string) => {
|
||||
return this.doFetch(
|
||||
`${this.urlVersion}/opengraph`,
|
||||
@@ -49,7 +51,7 @@ const ClientGeneral = (superclass: any) => class extends superclass {
|
||||
const url = `${this.urlVersion}/logs`;
|
||||
|
||||
if (!this.enableLogging) {
|
||||
throw new ClientError(this.client.baseUrl, {
|
||||
throw new ClientError(this.apiClient.baseUrl, {
|
||||
message: 'Logging disabled.',
|
||||
url,
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@ import {buildQueryString} from '@utils/helpers';
|
||||
|
||||
import {PER_PAGE_DEFAULT} from './constants';
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientGroupsMix {
|
||||
getGroup: (id: string) => Promise<Group>;
|
||||
getGroups: (params: {query?: string; filterAllowReference?: boolean; page?: number; perPage?: number; since?: number; includeMemberCount?: boolean}) => Promise<Group[]>;
|
||||
@@ -16,7 +18,7 @@ export interface ClientGroupsMix {
|
||||
getAllTeamsAssociatedToGroup: (groupId: string, filterAllowReference?: boolean) => Promise<{groupTeams: GroupTeam[]}>;
|
||||
}
|
||||
|
||||
const ClientGroups = (superclass: any) => class extends superclass {
|
||||
const ClientGroups = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
getGroup = async (id: string) => {
|
||||
return this.doFetch(
|
||||
`${this.urlVersion}/groups/${id}`,
|
||||
|
||||
@@ -5,6 +5,8 @@ import {buildQueryString} from '@utils/helpers';
|
||||
|
||||
import {PER_PAGE_DEFAULT} from './constants';
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientIntegrationsMix {
|
||||
getCommandsList: (teamId: string) => Promise<Command[]>;
|
||||
getCommandAutocompleteSuggestionsList: (userInput: string, teamId: string, channelId: string, rootId?: string) => Promise<AutocompleteSuggestion[]>;
|
||||
@@ -14,7 +16,7 @@ export interface ClientIntegrationsMix {
|
||||
submitInteractiveDialog: (data: DialogSubmission) => Promise<any>;
|
||||
}
|
||||
|
||||
const ClientIntegrations = (superclass: any) => class extends superclass {
|
||||
const ClientIntegrations = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
getCommandsList = async (teamId: string) => {
|
||||
return this.doFetch(
|
||||
`${this.getCommandsRoute()}?team_id=${teamId}`,
|
||||
@@ -37,7 +39,7 @@ const ClientIntegrations = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
executeCommand = async (command: string, commandArgs = {}) => {
|
||||
this.analytics.trackAPI('api_integrations_used');
|
||||
this.analytics?.trackAPI('api_integrations_used');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getCommandsRoute()}/execute`,
|
||||
@@ -46,7 +48,7 @@ const ClientIntegrations = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
addCommand = async (command: Command) => {
|
||||
this.analytics.trackAPI('api_integrations_created');
|
||||
this.analytics?.trackAPI('api_integrations_created');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getCommandsRoute()}`,
|
||||
@@ -55,7 +57,7 @@ const ClientIntegrations = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
submitInteractiveDialog = async (data: DialogSubmission) => {
|
||||
this.analytics.trackAPI('api_interactive_messages_dialog_submitted');
|
||||
this.analytics?.trackAPI('api_interactive_messages_dialog_submitted');
|
||||
return this.doFetch(
|
||||
`${this.urlVersion}/actions/dialogs/submit`,
|
||||
{method: 'post', body: data},
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
|
||||
import {General} from '@constants';
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientNPSMix {
|
||||
npsGiveFeedbackAction: () => Promise<Post>;
|
||||
}
|
||||
|
||||
const ClientNPS = (superclass: any) => class extends superclass {
|
||||
const ClientNPS = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
npsGiveFeedbackAction = async () => {
|
||||
return this.doFetch(
|
||||
`${this.getPluginRoute(General.NPS_PLUGIN_ID)}/api/v1/give_feedback`,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientPluginsMix {
|
||||
getPluginsManifests: () => Promise<ClientPluginManifest[]>;
|
||||
}
|
||||
|
||||
const ClientPlugins = (superclass: any) => class extends superclass {
|
||||
const ClientPlugins = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
getPluginsManifests = async () => {
|
||||
return this.doFetch(
|
||||
`${this.getPluginsRoute()}/webapp`,
|
||||
|
||||
@@ -5,6 +5,8 @@ import {buildQueryString} from '@utils/helpers';
|
||||
|
||||
import {PER_PAGE_DEFAULT} from './constants';
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientPostsMix {
|
||||
createPost: (post: Post) => Promise<Post>;
|
||||
updatePost: (post: Post) => Promise<Post>;
|
||||
@@ -31,12 +33,12 @@ export interface ClientPostsMix {
|
||||
doPostActionWithCookie: (postId: string, actionId: string, actionCookie: string, selectedOption?: string) => Promise<any>;
|
||||
}
|
||||
|
||||
const ClientPosts = (superclass: any) => class extends superclass {
|
||||
const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
createPost = async (post: Post) => {
|
||||
this.analytics.trackAPI('api_posts_create', {channel_id: post.channel_id});
|
||||
this.analytics?.trackAPI('api_posts_create', {channel_id: post.channel_id});
|
||||
|
||||
if (post.root_id != null && post.root_id !== '') {
|
||||
this.analytics.trackAPI('api_posts_replied', {channel_id: post.channel_id});
|
||||
this.analytics?.trackAPI('api_posts_replied', {channel_id: post.channel_id});
|
||||
}
|
||||
|
||||
return this.doFetch(
|
||||
@@ -46,7 +48,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
updatePost = async (post: Post) => {
|
||||
this.analytics.trackAPI('api_posts_update', {channel_id: post.channel_id});
|
||||
this.analytics?.trackAPI('api_posts_update', {channel_id: post.channel_id});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getPostRoute(post.id)}`,
|
||||
@@ -62,7 +64,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
patchPost = async (postPatch: Partial<Post> & {id: string}) => {
|
||||
this.analytics.trackAPI('api_posts_patch', {channel_id: postPatch.channel_id});
|
||||
this.analytics?.trackAPI('api_posts_patch', {channel_id: postPatch.channel_id});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getPostRoute(postPatch.id)}/patch`,
|
||||
@@ -71,7 +73,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
deletePost = async (postId: string) => {
|
||||
this.analytics.trackAPI('api_posts_delete');
|
||||
this.analytics?.trackAPI('api_posts_delete');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getPostRoute(postId)}`,
|
||||
@@ -79,7 +81,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
);
|
||||
};
|
||||
|
||||
getPostThread = (postId: string, options: FetchPaginatedThreadOptions): Promise<PostResponse> => {
|
||||
getPostThread = (postId: string, options: FetchPaginatedThreadOptions) => {
|
||||
const {
|
||||
fetchThreads = true,
|
||||
collapsedThreads = false,
|
||||
@@ -110,7 +112,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getPostsBefore = async (channelId: string, postId: string, page = 0, perPage = PER_PAGE_DEFAULT, collapsedThreads = false, collapsedThreadsExtended = false) => {
|
||||
this.analytics.trackAPI('api_posts_get_before', {channel_id: channelId});
|
||||
this.analytics?.trackAPI('api_posts_get_before', {channel_id: channelId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}/posts${buildQueryString({before: postId, page, per_page: perPage, collapsedThreads, collapsedThreadsExtended})}`,
|
||||
@@ -119,7 +121,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getPostsAfter = async (channelId: string, postId: string, page = 0, perPage = PER_PAGE_DEFAULT, collapsedThreads = false, collapsedThreadsExtended = false) => {
|
||||
this.analytics.trackAPI('api_posts_get_after', {channel_id: channelId});
|
||||
this.analytics?.trackAPI('api_posts_get_after', {channel_id: channelId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}/posts${buildQueryString({after: postId, page, per_page: perPage, collapsedThreads, collapsedThreadsExtended})}`,
|
||||
@@ -135,7 +137,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getSavedPosts = async (userId: string, channelId = '', teamId = '', page = 0, perPage = PER_PAGE_DEFAULT) => {
|
||||
this.analytics.trackAPI('api_posts_get_flagged', {team_id: teamId});
|
||||
this.analytics?.trackAPI('api_posts_get_flagged', {team_id: teamId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUserRoute(userId)}/posts/flagged${buildQueryString({channel_id: channelId, team_id: teamId, page, per_page: perPage})}`,
|
||||
@@ -144,7 +146,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getPinnedPosts = async (channelId: string) => {
|
||||
this.analytics.trackAPI('api_posts_get_pinned', {channel_id: channelId});
|
||||
this.analytics?.trackAPI('api_posts_get_pinned', {channel_id: channelId});
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}/pinned`,
|
||||
{method: 'get'},
|
||||
@@ -152,7 +154,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
markPostAsUnread = async (userId: string, postId: string) => {
|
||||
this.analytics.trackAPI('api_post_set_unread_post');
|
||||
this.analytics?.trackAPI('api_post_set_unread_post');
|
||||
|
||||
// collapsed_threads_supported is not based on user preferences but to know if "CLIENT" supports CRT
|
||||
const body = JSON.stringify({collapsed_threads_supported: true});
|
||||
@@ -164,7 +166,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
pinPost = async (postId: string) => {
|
||||
this.analytics.trackAPI('api_posts_pin');
|
||||
this.analytics?.trackAPI('api_posts_pin');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getPostRoute(postId)}/pin`,
|
||||
@@ -173,7 +175,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
unpinPost = async (postId: string) => {
|
||||
this.analytics.trackAPI('api_posts_unpin');
|
||||
this.analytics?.trackAPI('api_posts_unpin');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getPostRoute(postId)}/unpin`,
|
||||
@@ -182,7 +184,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
addReaction = async (userId: string, postId: string, emojiName: string) => {
|
||||
this.analytics.trackAPI('api_reactions_save', {post_id: postId});
|
||||
this.analytics?.trackAPI('api_reactions_save', {post_id: postId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getReactionsRoute()}`,
|
||||
@@ -191,7 +193,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
removeReaction = async (userId: string, postId: string, emojiName: string) => {
|
||||
this.analytics.trackAPI('api_reactions_delete', {post_id: postId});
|
||||
this.analytics?.trackAPI('api_reactions_delete', {post_id: postId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUserRoute(userId)}/posts/${postId}/reactions/${emojiName}`,
|
||||
@@ -207,7 +209,7 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
searchPostsWithParams = async (teamId: string, params: PostSearchParams) => {
|
||||
this.analytics.trackAPI('api_posts_search');
|
||||
this.analytics?.trackAPI('api_posts_search');
|
||||
const endpoint = teamId ? `${this.getTeamRoute(teamId)}/posts/search` : `${this.getPostsRoute()}/search`;
|
||||
return this.doFetch(endpoint, {method: 'post', body: params});
|
||||
};
|
||||
@@ -222,9 +224,9 @@ const ClientPosts = (superclass: any) => class extends superclass {
|
||||
|
||||
doPostActionWithCookie = async (postId: string, actionId: string, actionCookie: string, selectedOption = '') => {
|
||||
if (selectedOption) {
|
||||
this.analytics.trackAPI('api_interactive_messages_menu_selected');
|
||||
this.analytics?.trackAPI('api_interactive_messages_menu_selected');
|
||||
} else {
|
||||
this.analytics.trackAPI('api_interactive_messages_button_clicked');
|
||||
this.analytics?.trackAPI('api_interactive_messages_button_clicked');
|
||||
}
|
||||
|
||||
const msg: any = {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientPreferencesMix {
|
||||
savePreferences: (userId: string, preferences: PreferenceType[]) => Promise<any>;
|
||||
deletePreferences: (userId: string, preferences: PreferenceType[]) => Promise<any>;
|
||||
getMyPreferences: () => Promise<PreferenceType[]>;
|
||||
}
|
||||
|
||||
const ClientPreferences = (superclass: any) => class extends superclass {
|
||||
const ClientPreferences = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
savePreferences = async (userId: string, preferences: PreferenceType[]) => {
|
||||
this.analytics.trackAPI('action_posts_flag');
|
||||
this.analytics?.trackAPI('action_posts_flag');
|
||||
return this.doFetch(
|
||||
`${this.getPreferencesRoute(userId)}`,
|
||||
{method: 'put', body: preferences},
|
||||
@@ -24,7 +26,7 @@ const ClientPreferences = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
deletePreferences = async (userId: string, preferences: PreferenceType[]) => {
|
||||
this.analytics.trackAPI('action_posts_unflag');
|
||||
this.analytics?.trackAPI('action_posts_unflag');
|
||||
return this.doFetch(
|
||||
`${this.getPreferencesRoute(userId)}/delete`,
|
||||
{method: 'post', body: preferences},
|
||||
|
||||
@@ -5,6 +5,8 @@ import {buildQueryString} from '@utils/helpers';
|
||||
|
||||
import {PER_PAGE_DEFAULT} from './constants';
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientTeamsMix {
|
||||
createTeam: (team: Team) => Promise<Team>;
|
||||
deleteTeam: (teamId: string) => Promise<any>;
|
||||
@@ -28,9 +30,9 @@ export interface ClientTeamsMix {
|
||||
getTeamIconUrl: (teamId: string, lastTeamIconUpdate: number) => string;
|
||||
}
|
||||
|
||||
const ClientTeams = (superclass: any) => class extends superclass {
|
||||
const ClientTeams = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
createTeam = async (team: Team) => {
|
||||
this.analytics.trackAPI('api_teams_create');
|
||||
this.analytics?.trackAPI('api_teams_create');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getTeamsRoute()}`,
|
||||
@@ -39,7 +41,7 @@ const ClientTeams = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
deleteTeam = async (teamId: string) => {
|
||||
this.analytics.trackAPI('api_teams_delete');
|
||||
this.analytics?.trackAPI('api_teams_delete');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getTeamRoute(teamId)}`,
|
||||
@@ -48,7 +50,7 @@ const ClientTeams = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
updateTeam = async (team: Team) => {
|
||||
this.analytics.trackAPI('api_teams_update_name', {team_id: team.id});
|
||||
this.analytics?.trackAPI('api_teams_update_name', {team_id: team.id});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getTeamRoute(team.id)}`,
|
||||
@@ -57,7 +59,7 @@ const ClientTeams = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
patchTeam = async (team: Partial<Team> & {id: string}) => {
|
||||
this.analytics.trackAPI('api_teams_patch_name', {team_id: team.id});
|
||||
this.analytics?.trackAPI('api_teams_patch_name', {team_id: team.id});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getTeamRoute(team.id)}/patch`,
|
||||
@@ -80,7 +82,7 @@ const ClientTeams = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getTeamByName = async (teamName: string) => {
|
||||
this.analytics.trackAPI('api_teams_get_team_by_name');
|
||||
this.analytics?.trackAPI('api_teams_get_team_by_name');
|
||||
|
||||
return this.doFetch(
|
||||
this.getTeamNameRoute(teamName),
|
||||
@@ -131,7 +133,7 @@ const ClientTeams = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
addToTeam = async (teamId: string, userId: string) => {
|
||||
this.analytics.trackAPI('api_teams_invite_members', {team_id: teamId});
|
||||
this.analytics?.trackAPI('api_teams_invite_members', {team_id: teamId});
|
||||
|
||||
const member = {user_id: userId, team_id: teamId};
|
||||
return this.doFetch(
|
||||
@@ -141,7 +143,7 @@ const ClientTeams = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
addUsersToTeamGracefully = (teamId: string, userIds: string[]) => {
|
||||
this.analytics.trackAPI('api_teams_batch_add_members', {team_id: teamId, count: userIds.length});
|
||||
this.analytics?.trackAPI('api_teams_batch_add_members', {team_id: teamId, count: userIds.length});
|
||||
|
||||
const members: Array<{team_id: string; user_id: string}> = [];
|
||||
userIds.forEach((id) => members.push({team_id: teamId, user_id: id}));
|
||||
@@ -153,7 +155,7 @@ const ClientTeams = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
sendEmailInvitesToTeamGracefully = (teamId: string, emails: string[]) => {
|
||||
this.analytics.trackAPI('api_teams_invite_members', {team_id: teamId});
|
||||
this.analytics?.trackAPI('api_teams_invite_members', {team_id: teamId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getTeamRoute(teamId)}/invite/email?graceful=true`,
|
||||
@@ -170,7 +172,7 @@ const ClientTeams = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
removeFromTeam = async (teamId: string, userId: string) => {
|
||||
this.analytics.trackAPI('api_teams_remove_members', {team_id: teamId});
|
||||
this.analytics?.trackAPI('api_teams_remove_members', {team_id: teamId});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getTeamMemberRoute(teamId, userId)}`,
|
||||
|
||||
@@ -5,6 +5,8 @@ import {buildQueryString, isMinimumServerVersion} from '@utils/helpers';
|
||||
|
||||
import {PER_PAGE_DEFAULT} from './constants';
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientThreadsMix {
|
||||
getThreads: (userId: string, teamId: string, before?: string, after?: string, pageSize?: number, deleted?: boolean, unread?: boolean, since?: number, totalsOnly?: boolean, serverVersion?: string) => Promise<GetUserThreadsResponse>;
|
||||
getThread: (userId: string, teamId: string, threadId: string, extended?: boolean) => Promise<any>;
|
||||
@@ -14,7 +16,7 @@ export interface ClientThreadsMix {
|
||||
updateThreadFollow: (userId: string, teamId: string, threadId: string, state: boolean) => Promise<any>;
|
||||
}
|
||||
|
||||
const ClientThreads = (superclass: any) => class extends superclass {
|
||||
const ClientThreads = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
getThreads = async (userId: string, teamId: string, before = '', after = '', pageSize = PER_PAGE_DEFAULT, deleted = false, unread = false, since = 0, totalsOnly = false, serverVersion = '') => {
|
||||
const queryStringObj: Record<string, any> = {
|
||||
extended: 'true',
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientTosMix {
|
||||
updateMyTermsOfServiceStatus: (termsOfServiceId: string, accepted: boolean) => Promise<{status: string}>;
|
||||
getTermsOfService: () => Promise<TermsOfService>;
|
||||
}
|
||||
|
||||
const ClientTos = (superclass: any) => class extends superclass {
|
||||
const ClientTos = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
updateMyTermsOfServiceStatus = async (termsOfServiceId: string, accepted: boolean) => {
|
||||
return this.doFetch(
|
||||
`${this.getUserRoute('me')}/terms_of_service`,
|
||||
|
||||
@@ -6,6 +6,8 @@ import {buildQueryString} from '@utils/helpers';
|
||||
|
||||
import {PER_PAGE_DEFAULT} from './constants';
|
||||
|
||||
import type ClientBase from './base';
|
||||
|
||||
export interface ClientUsersMix {
|
||||
createUser: (user: UserProfile, token: string, inviteId: string) => Promise<UserProfile>;
|
||||
patchMe: (userPatch: Partial<UserProfile>) => Promise<UserProfile>;
|
||||
@@ -24,7 +26,7 @@ export interface ClientUsersMix {
|
||||
getProfilesInTeam: (teamId: string, page?: number, perPage?: number, sort?: string, options?: Record<string, any>) => Promise<UserProfile[]>;
|
||||
getProfilesNotInTeam: (teamId: string, groupConstrained: boolean, page?: number, perPage?: number) => Promise<UserProfile[]>;
|
||||
getProfilesWithoutTeam: (page?: number, perPage?: number, options?: Record<string, any>) => Promise<UserProfile[]>;
|
||||
getProfilesInChannel: (channelId: string, page?: number, perPage?: number, sort?: string) => Promise<UserProfile[]>;
|
||||
getProfilesInChannel: (channelId: string, options?: GetUsersOptions) => Promise<UserProfile[]>;
|
||||
getProfilesInGroupChannels: (channelsIds: string[]) => Promise<{[x: string]: UserProfile[]}>;
|
||||
getProfilesNotInChannel: (teamId: string, channelId: string, groupConstrained: boolean, page?: number, perPage?: number) => Promise<UserProfile[]>;
|
||||
getMe: () => Promise<UserProfile>;
|
||||
@@ -37,7 +39,7 @@ export interface ClientUsersMix {
|
||||
getSessions: (userId: string) => Promise<Session[]>;
|
||||
checkUserMfa: (loginId: string) => Promise<{mfa_required: boolean}>;
|
||||
attachDevice: (deviceId: string) => Promise<any>;
|
||||
searchUsers: (term: string, options: any) => Promise<UserProfile[]>;
|
||||
searchUsers: (term: string, options: SearchUserOptions) => Promise<UserProfile[]>;
|
||||
getStatusesByIds: (userIds: string[]) => Promise<UserStatus[]>;
|
||||
getStatus: (userId: string) => Promise<UserStatus>;
|
||||
updateStatus: (status: UserStatus) => Promise<UserStatus>;
|
||||
@@ -46,9 +48,9 @@ export interface ClientUsersMix {
|
||||
removeRecentCustomStatus: (customStatus: UserCustomStatus) => Promise<{status: string}>;
|
||||
}
|
||||
|
||||
const ClientUsers = (superclass: any) => class extends superclass {
|
||||
const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
|
||||
createUser = async (user: UserProfile, token: string, inviteId: string) => {
|
||||
this.analytics.trackAPI('api_users_create');
|
||||
this.analytics?.trackAPI('api_users_create');
|
||||
|
||||
const queryParams: any = {};
|
||||
|
||||
@@ -74,7 +76,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
patchUser = async (userPatch: Partial<UserProfile> & {id: string}) => {
|
||||
this.analytics.trackAPI('api_users_patch');
|
||||
this.analytics?.trackAPI('api_users_patch');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUserRoute(userPatch.id)}/patch`,
|
||||
@@ -83,7 +85,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
updateUser = async (user: UserProfile) => {
|
||||
this.analytics.trackAPI('api_users_update');
|
||||
this.analytics?.trackAPI('api_users_update');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUserRoute(user.id)}`,
|
||||
@@ -92,7 +94,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
demoteUserToGuest = async (userId: string) => {
|
||||
this.analytics.trackAPI('api_users_demote_user_to_guest');
|
||||
this.analytics?.trackAPI('api_users_demote_user_to_guest');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUserRoute(userId)}/demote`,
|
||||
@@ -101,7 +103,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getKnownUsers = async () => {
|
||||
this.analytics.trackAPI('api_get_known_users');
|
||||
this.analytics?.trackAPI('api_get_known_users');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}/known`,
|
||||
@@ -110,7 +112,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
sendPasswordResetEmail = async (email: string) => {
|
||||
this.analytics.trackAPI('api_users_send_password_reset');
|
||||
this.analytics?.trackAPI('api_users_send_password_reset');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}/password/reset/send`,
|
||||
@@ -119,7 +121,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
setDefaultProfileImage = async (userId: string) => {
|
||||
this.analytics.trackAPI('api_users_set_default_profile_picture');
|
||||
this.analytics?.trackAPI('api_users_set_default_profile_picture');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUserRoute(userId)}/image`,
|
||||
@@ -128,10 +130,10 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
login = async (loginId: string, password: string, token = '', deviceId = '', ldapOnly = false) => {
|
||||
this.analytics.trackAPI('api_users_login');
|
||||
this.analytics?.trackAPI('api_users_login');
|
||||
|
||||
if (ldapOnly) {
|
||||
this.analytics.trackAPI('api_users_login_ldap');
|
||||
this.analytics?.trackAPI('api_users_login_ldap');
|
||||
}
|
||||
|
||||
const body: any = {
|
||||
@@ -159,7 +161,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
loginById = async (id: string, password: string, token = '', deviceId = '') => {
|
||||
this.analytics.trackAPI('api_users_login');
|
||||
this.analytics?.trackAPI('api_users_login');
|
||||
const body: any = {
|
||||
device_id: deviceId,
|
||||
id,
|
||||
@@ -181,7 +183,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
logout = async () => {
|
||||
this.analytics.trackAPI('api_users_logout');
|
||||
this.analytics?.trackAPI('api_users_logout');
|
||||
|
||||
const response = await this.doFetch(
|
||||
`${this.getUsersRoute()}/logout`,
|
||||
@@ -192,7 +194,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getProfiles = async (page = 0, perPage = PER_PAGE_DEFAULT, options = {}) => {
|
||||
this.analytics.trackAPI('api_profiles_get');
|
||||
this.analytics?.trackAPI('api_profiles_get');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}${buildQueryString({page, per_page: perPage, ...options})}`,
|
||||
@@ -201,7 +203,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getProfilesByIds = async (userIds: string[], options = {}) => {
|
||||
this.analytics.trackAPI('api_profiles_get_by_ids');
|
||||
this.analytics?.trackAPI('api_profiles_get_by_ids');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}/ids${buildQueryString(options)}`,
|
||||
@@ -210,7 +212,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getProfilesByUsernames = async (usernames: string[]) => {
|
||||
this.analytics.trackAPI('api_profiles_get_by_usernames');
|
||||
this.analytics?.trackAPI('api_profiles_get_by_usernames');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}/usernames`,
|
||||
@@ -219,7 +221,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getProfilesInTeam = async (teamId: string, page = 0, perPage = PER_PAGE_DEFAULT, sort = '', options = {}) => {
|
||||
this.analytics.trackAPI('api_profiles_get_in_team', {team_id: teamId, sort});
|
||||
this.analytics?.trackAPI('api_profiles_get_in_team', {team_id: teamId, sort});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}${buildQueryString({...options, in_team: teamId, page, per_page: perPage, sort})}`,
|
||||
@@ -228,7 +230,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getProfilesNotInTeam = async (teamId: string, groupConstrained: boolean, page = 0, perPage = PER_PAGE_DEFAULT) => {
|
||||
this.analytics.trackAPI('api_profiles_get_not_in_team', {team_id: teamId, group_constrained: groupConstrained});
|
||||
this.analytics?.trackAPI('api_profiles_get_not_in_team', {team_id: teamId, group_constrained: groupConstrained});
|
||||
|
||||
const queryStringObj: any = {not_in_team: teamId, page, per_page: perPage};
|
||||
if (groupConstrained) {
|
||||
@@ -242,7 +244,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getProfilesWithoutTeam = async (page = 0, perPage = PER_PAGE_DEFAULT, options = {}) => {
|
||||
this.analytics.trackAPI('api_profiles_get_without_team');
|
||||
this.analytics?.trackAPI('api_profiles_get_without_team');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}${buildQueryString({...options, without_team: 1, page, per_page: perPage})}`,
|
||||
@@ -250,10 +252,10 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
);
|
||||
};
|
||||
|
||||
getProfilesInChannel = async (channelId: string, page = 0, perPage = PER_PAGE_DEFAULT, sort = '') => {
|
||||
this.analytics.trackAPI('api_profiles_get_in_channel', {channel_id: channelId});
|
||||
getProfilesInChannel = async (channelId: string, options: GetUsersOptions) => {
|
||||
this.analytics?.trackAPI('api_profiles_get_in_channel', {channel_id: channelId});
|
||||
|
||||
const queryStringObj = {in_channel: channelId, page, per_page: perPage, sort};
|
||||
const queryStringObj = {in_channel: channelId, ...options};
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}${buildQueryString(queryStringObj)}`,
|
||||
{method: 'get'},
|
||||
@@ -261,7 +263,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getProfilesInGroupChannels = async (channelsIds: string[]) => {
|
||||
this.analytics.trackAPI('api_profiles_get_in_group_channels', {channelsIds});
|
||||
this.analytics?.trackAPI('api_profiles_get_in_group_channels', {channelsIds});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}/group_channels`,
|
||||
@@ -270,7 +272,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
getProfilesNotInChannel = async (teamId: string, channelId: string, groupConstrained: boolean, page = 0, perPage = PER_PAGE_DEFAULT) => {
|
||||
this.analytics.trackAPI('api_profiles_get_not_in_channel', {team_id: teamId, channel_id: channelId, group_constrained: groupConstrained});
|
||||
this.analytics?.trackAPI('api_profiles_get_not_in_channel', {team_id: teamId, channel_id: channelId, group_constrained: groupConstrained});
|
||||
|
||||
const queryStringObj: any = {in_team: teamId, not_in_channel: channelId, page, per_page: perPage};
|
||||
if (groupConstrained) {
|
||||
@@ -372,7 +374,7 @@ const ClientUsers = (superclass: any) => class extends superclass {
|
||||
};
|
||||
|
||||
searchUsers = async (term: string, options: any) => {
|
||||
this.analytics.trackAPI('api_search_users');
|
||||
this.analytics?.trackAPI('api_search_users');
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getUsersRoute()}/search`,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
import {queryAllCustomEmojis} from '@queries/servers/custom_emoji';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {queryEmojiPreferences} from '@queries/servers/preference';
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
|
||||
import EmojiSuggestion from './emoji_suggestion';
|
||||
@@ -21,12 +21,9 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const isCustomEmojisEnabled = observeConfigBooleanValue(database, 'EnableCustomEmoji');
|
||||
return {
|
||||
customEmojis: isCustomEmojisEnabled.pipe(
|
||||
switchMap((enabled) => (enabled ?
|
||||
queryAllCustomEmojis(database).observe() :
|
||||
of$(emptyEmojiList)),
|
||||
),
|
||||
switchMap((enabled) => (enabled ? queryAllCustomEmojis(database).observe() : of$(emptyEmojiList))),
|
||||
),
|
||||
skinTone: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_EMOJI, Preferences.EMOJI_SKINTONE).
|
||||
skinTone: queryEmojiPreferences(database, Preferences.EMOJI_SKINTONE).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap((prefs) => of$(prefs?.[0]?.value ?? 'default')),
|
||||
),
|
||||
|
||||
@@ -23,7 +23,7 @@ type Props = {
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
const LeaveChanelLabel = ({canLeave, channelId, displayName, isOptionItem, type, testID}: Props) => {
|
||||
const LeaveChannelLabel = ({canLeave, channelId, displayName, isOptionItem, type, testID}: Props) => {
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const isTablet = useIsTablet();
|
||||
@@ -183,4 +183,4 @@ const LeaveChanelLabel = ({canLeave, channelId, displayName, isOptionItem, type,
|
||||
);
|
||||
};
|
||||
|
||||
export default LeaveChanelLabel;
|
||||
export default LeaveChannelLabel;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {queryUsersById} from '@queries/servers/user';
|
||||
|
||||
import ManageMembersLabel from './manage_members_label';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
type OwnProps = WithDatabaseArgs & {
|
||||
isDefaultChannel: boolean;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
const enhanced = withObservables(['isDefaultChannel', 'userId'], ({isDefaultChannel, userId, database}: OwnProps) => {
|
||||
const users = queryUsersById(database, [userId]).observe();
|
||||
const canRemoveUser = users.pipe(
|
||||
switchMap((u) => {
|
||||
return of$(!isDefaultChannel || (isDefaultChannel && u[0].isGuest));
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
canRemoveUser,
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(enhanced(ManageMembersLabel));
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {Alert, DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {fetchChannelStats, removeMemberFromChannel, updateChannelMemberSchemeRoles} from '@actions/remote/channel';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {Events, Members} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {t} from '@i18n';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
import {alertErrorWithFallback} from '@utils/draft';
|
||||
|
||||
import type {ManageOptionsTypes} from '@constants/members';
|
||||
|
||||
const {MAKE_CHANNEL_ADMIN, MAKE_CHANNEL_MEMBER, REMOVE_USER} = Members.ManageOptions;
|
||||
|
||||
const messages = defineMessages({
|
||||
role_change_error: {
|
||||
id: t('mobile.manage_members.change_role.error'),
|
||||
defaultMessage: 'An error occurred while trying to update the role. Please check your connection and try again.',
|
||||
},
|
||||
make_channel_admin: {
|
||||
id: t('mobile.manage_members.make_channel_admin'),
|
||||
defaultMessage: 'Make Channel Admin',
|
||||
},
|
||||
make_channel_member: {
|
||||
id: t('mobile.manage_members.make_channel_member'),
|
||||
defaultMessage: 'Make Channel Member',
|
||||
},
|
||||
remove_title: {
|
||||
id: t('mobile.manage_members.remove_member'),
|
||||
defaultMessage: 'Remove From Channel',
|
||||
},
|
||||
remove_message: {
|
||||
id: t('mobile.manage_members.message'),
|
||||
defaultMessage: 'Are you sure you want to remove the selected member from the channel?',
|
||||
},
|
||||
remove_cancel: {
|
||||
id: t('mobile.manage_members.cancel'),
|
||||
defaultMessage: 'Cancel',
|
||||
},
|
||||
remove_confirm: {
|
||||
id: t('mobile.manage_members.remove'),
|
||||
defaultMessage: 'Remove',
|
||||
},
|
||||
});
|
||||
|
||||
type Props = {
|
||||
canRemoveUser: boolean;
|
||||
channelId: string;
|
||||
manageOption: ManageOptionsTypes;
|
||||
testID?: string;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
const ManageMembersLabel = ({canRemoveUser, channelId, manageOption, testID, userId}: Props) => {
|
||||
const intl = useIntl();
|
||||
const {formatMessage} = intl;
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const handleRemoveUser = useCallback(async () => {
|
||||
removeMemberFromChannel(serverUrl, channelId, userId);
|
||||
fetchChannelStats(serverUrl, channelId, false);
|
||||
await dismissBottomSheet();
|
||||
DeviceEventEmitter.emit(Events.REMOVE_USER_FROM_CHANNEL, userId);
|
||||
}, [channelId, serverUrl, userId]);
|
||||
|
||||
const removeFromChannel = useCallback(() => {
|
||||
Alert.alert(
|
||||
formatMessage(messages.remove_title),
|
||||
formatMessage(messages.remove_message),
|
||||
[{
|
||||
text: formatMessage(messages.remove_cancel),
|
||||
style: 'cancel',
|
||||
}, {
|
||||
text: formatMessage(messages.remove_confirm),
|
||||
style: 'destructive',
|
||||
onPress: handleRemoveUser,
|
||||
}], {cancelable: false},
|
||||
);
|
||||
}, [formatMessage, handleRemoveUser]);
|
||||
|
||||
const updateChannelMemberSchemeRole = useCallback(async (schemeAdmin: boolean) => {
|
||||
const result = await updateChannelMemberSchemeRoles(serverUrl, channelId, userId, true, schemeAdmin);
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(intl, result.error, messages.role_change_error);
|
||||
}
|
||||
await dismissBottomSheet();
|
||||
DeviceEventEmitter.emit(Events.MANAGE_USER_CHANGE_ROLE, {userId, schemeAdmin});
|
||||
}, [channelId, userId, intl, serverUrl]);
|
||||
|
||||
const onAction = useCallback(() => {
|
||||
switch (manageOption) {
|
||||
case REMOVE_USER:
|
||||
removeFromChannel();
|
||||
break;
|
||||
case MAKE_CHANNEL_ADMIN:
|
||||
updateChannelMemberSchemeRole(true);
|
||||
break;
|
||||
case MAKE_CHANNEL_MEMBER:
|
||||
updateChannelMemberSchemeRole(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, [manageOption, removeFromChannel, updateChannelMemberSchemeRole]);
|
||||
|
||||
let actionText;
|
||||
let icon;
|
||||
let isDestructive = false;
|
||||
switch (manageOption) {
|
||||
case REMOVE_USER:
|
||||
actionText = (formatMessage(messages.remove_title));
|
||||
icon = 'trash-can-outline';
|
||||
isDestructive = true;
|
||||
break;
|
||||
case MAKE_CHANNEL_ADMIN:
|
||||
actionText = formatMessage(messages.make_channel_admin);
|
||||
icon = 'account-outline';
|
||||
break;
|
||||
case MAKE_CHANNEL_MEMBER:
|
||||
actionText = formatMessage(messages.make_channel_member);
|
||||
icon = 'account-outline';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (manageOption === REMOVE_USER && !canRemoveUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!actionText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<OptionItem
|
||||
action={onAction}
|
||||
destructive={isDestructive}
|
||||
icon={icon}
|
||||
label={actionText}
|
||||
testID={testID}
|
||||
type='default'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManageMembersLabel;
|
||||
@@ -10,7 +10,7 @@ import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
import {observeChannelsWithCalls} from '@calls/state';
|
||||
import {General} from '@constants';
|
||||
import {withServerUrl} from '@context/server';
|
||||
import {observeChannelSettings, observeMyChannel} from '@queries/servers/channel';
|
||||
import {observeChannelSettings, observeMyChannel, queryChannelMembers} from '@queries/servers/channel';
|
||||
import {queryDraft} from '@queries/servers/drafts';
|
||||
import {observeCurrentChannelId, observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeTeam} from '@queries/servers/team';
|
||||
@@ -67,7 +67,7 @@ const enhance = withObservables(['channel', 'showTeamName'], ({
|
||||
|
||||
let membersCount = of$(0);
|
||||
if (channel.type === General.GM_CHANNEL) {
|
||||
membersCount = channel.members.observeCount(false);
|
||||
membersCount = queryChannelMembers(database, channel.id).observeCount(false);
|
||||
}
|
||||
|
||||
const isUnread = myChannel.pipe(
|
||||
|
||||
@@ -6,6 +6,7 @@ import withObservables from '@nozbe/with-observables';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeChannel} from '@queries/servers/channel';
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {observeTeam} from '@queries/servers/team';
|
||||
|
||||
@@ -17,7 +18,7 @@ import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
const enhanced = withObservables(['post'], ({post, database}: WithDatabaseArgs & { post: PostModel }) => {
|
||||
const currentTeamId = observeCurrentTeamId(database);
|
||||
const channel = post.channel.observe();
|
||||
const channel = observeChannel(database, post.id);
|
||||
|
||||
const teamName = combineLatest([channel, currentTeamId]).pipe(
|
||||
switchMap(([c, tid]) => {
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
|
||||
import {observeTeamIdByThread} from '@queries/servers/thread';
|
||||
|
||||
import FollowThreadOption from './follow_thread_option';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ThreadModel from '@typings/database/models/servers/thread';
|
||||
|
||||
const enhanced = withObservables(['thread'], ({thread}: { thread: ThreadModel }) => {
|
||||
const enhanced = withObservables(['thread'], ({thread, database}: {thread: ThreadModel} & WithDatabaseArgs) => {
|
||||
return {
|
||||
teamId: observeTeamIdByThread(thread),
|
||||
teamId: observeTeamIdByThread(database, thread),
|
||||
};
|
||||
});
|
||||
|
||||
export default enhanced(FollowThreadOption);
|
||||
export default withDatabase(enhanced(FollowThreadOption));
|
||||
|
||||
@@ -12,9 +12,8 @@ import {switchMap} from 'rxjs/operators';
|
||||
import FormattedDate from '@components/formatted_date';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import FormattedTime from '@components/formatted_time';
|
||||
import {Preferences} from '@constants';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference';
|
||||
import {queryDisplayNamePreferences} from '@queries/servers/preference';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
import {getCurrentMomentForTimezone} from '@utils/helpers';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
@@ -134,10 +133,10 @@ const CustomStatusExpiry = ({currentUser, isMilitaryTime, showPrefix, showTimeCo
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
currentUser: observeCurrentUser(database),
|
||||
isMilitaryTime: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).
|
||||
isMilitaryTime: queryDisplayNamePreferences(database).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap(
|
||||
(preferences) => of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)),
|
||||
(preferences) => of$(getDisplayNamePreferenceAsBool(preferences, 'use_military_time')),
|
||||
),
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {combineLatest, of as of$, from as from$} from 'rxjs';
|
||||
import {map, switchMap} from 'rxjs/operators';
|
||||
import {of as of$, from as from$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeConfigBooleanValue, observeLicense} from '@queries/servers/system';
|
||||
import {queryFilesForPost} from '@queries/servers/file';
|
||||
import {observeCanDownloadFiles, observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {fileExists} from '@utils/file';
|
||||
|
||||
import Files from './files';
|
||||
@@ -36,23 +37,14 @@ const filesLocalPathValidation = async (files: FileModel[], authorId: string) =>
|
||||
};
|
||||
|
||||
const enhance = withObservables(['post'], ({database, post}: EnhanceProps) => {
|
||||
const enableMobileFileDownload = observeConfigBooleanValue(database, 'EnableMobileFileDownload');
|
||||
const publicLinkEnabled = observeConfigBooleanValue(database, 'EnablePublicLink');
|
||||
|
||||
const complianceDisabled = observeLicense(database).pipe(
|
||||
switchMap((lcs) => of$(lcs?.IsLicensed === 'false' || lcs?.Compliance === 'false')),
|
||||
);
|
||||
|
||||
const canDownloadFiles = combineLatest([enableMobileFileDownload, complianceDisabled]).pipe(
|
||||
map(([download, compliance]) => compliance || download),
|
||||
);
|
||||
|
||||
const filesInfo = post.files.observeWithColumns(['local_path']).pipe(
|
||||
const filesInfo = queryFilesForPost(database, post.id).observeWithColumns(['local_path']).pipe(
|
||||
switchMap((fs) => from$(filesLocalPathValidation(fs, post.userId))),
|
||||
);
|
||||
|
||||
return {
|
||||
canDownloadFiles,
|
||||
canDownloadFiles: observeCanDownloadFiles(database),
|
||||
postId: of$(post.id),
|
||||
publicLinkEnabled,
|
||||
filesInfo,
|
||||
|
||||
@@ -7,7 +7,7 @@ import React from 'react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
|
||||
import {observeChannel} from '@queries/servers/channel';
|
||||
import {observeChannel, observeChannelInfo} from '@queries/servers/channel';
|
||||
import {observeConfigBooleanValue, observeConfigIntValue} from '@queries/servers/system';
|
||||
|
||||
import PostInput from './post_input';
|
||||
@@ -31,7 +31,7 @@ const enhanced = withObservables(['channelId'], ({database, channelId}: WithData
|
||||
);
|
||||
|
||||
const membersInChannel = channel.pipe(
|
||||
switchMap((c) => (c ? c.info.observe() : of$({memberCount: 0}))),
|
||||
switchMap((c) => (c ? observeChannelInfo(database, c.id) : of$({memberCount: 0}))),
|
||||
switchMap((i: ChannelInfoModel) => of$(i.memberCount)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {General, Permissions} from '@constants';
|
||||
import {MAX_MESSAGE_LENGTH_FALLBACK} from '@constants/post_draft';
|
||||
import {observeChannel, observeCurrentChannel} from '@queries/servers/channel';
|
||||
import {observeChannel, observeChannelInfo, observeCurrentChannel} from '@queries/servers/channel';
|
||||
import {queryAllCustomEmojis} from '@queries/servers/custom_emoji';
|
||||
import {observePermissionForChannel} from '@queries/servers/role';
|
||||
import {observeConfigBooleanValue, observeConfigIntValue, observeCurrentUserId} from '@queries/servers/system';
|
||||
@@ -56,7 +56,7 @@ const enhanced = withObservables([], (ownProps: WithDatabaseArgs & OwnProps) =>
|
||||
}),
|
||||
);
|
||||
|
||||
const channelInfo = channel.pipe(switchMap((c) => (c ? c.info.observe() : of$(undefined))));
|
||||
const channelInfo = channel.pipe(switchMap((c) => (c ? observeChannelInfo(database, c.id) : of$(undefined))));
|
||||
const membersCount = channelInfo.pipe(
|
||||
switchMap((i) => (i ? of$(i.memberCount) : of$(0))),
|
||||
);
|
||||
|
||||
@@ -6,19 +6,19 @@ import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeChannel} from '@queries/servers/channel';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
|
||||
import AddMembers from './add_members';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
const enhance = withObservables(['post'], ({database, post}: WithDatabaseArgs & {post: PostModel}) => ({
|
||||
currentUser: observeCurrentUser(database),
|
||||
channelType: post.channel.observe().pipe(
|
||||
channelType: observeChannel(database, post.channelId).pipe(
|
||||
switchMap(
|
||||
(channel: ChannelModel) => (channel ? of$(channel.type) : of$(null)),
|
||||
(channel) => (channel ? of$(channel.type) : of$(null)),
|
||||
),
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -12,6 +12,7 @@ import {handleBindingClick, postEphemeralCallResponseForPost} from '@actions/rem
|
||||
import {handleGotoLocation} from '@actions/remote/command';
|
||||
import {AppBindingLocations, AppCallResponseTypes} from '@constants/apps';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {observeChannel} from '@queries/servers/channel';
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {showAppForm} from '@screens/navigation';
|
||||
import {createCallContext} from '@utils/apps';
|
||||
@@ -22,7 +23,6 @@ import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
import ButtonBindingText from './button_binding_text';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
type Props = {
|
||||
@@ -135,7 +135,7 @@ const ButtonBinding = ({currentTeamId, binding, post, teamID, theme}: Props) =>
|
||||
};
|
||||
|
||||
const withTeamId = withObservables(['post'], ({post, database}: {post: PostModel} & WithDatabaseArgs) => ({
|
||||
teamID: post.channel.observe().pipe(map((channel: ChannelModel) => channel.teamId)),
|
||||
teamID: observeChannel(database, post.channelId).pipe(map((channel) => channel?.teamId)),
|
||||
currentTeamId: observeCurrentTeamId(database),
|
||||
}));
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ import {postEphemeralCallResponseForPost} from '@actions/remote/apps';
|
||||
import AutocompleteSelector from '@components/autocomplete_selector';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useAppBinding} from '@hooks/apps';
|
||||
import {observeChannel} from '@queries/servers/channel';
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
type Props = {
|
||||
@@ -79,7 +79,7 @@ const MenuBinding = ({binding, currentTeamId, post, teamID}: Props) => {
|
||||
};
|
||||
|
||||
const withTeamId = withObservables(['post'], ({post, database}: {post: PostModel} & WithDatabaseArgs) => ({
|
||||
teamID: post.channel.observe().pipe(map((channel: ChannelModel) => channel.teamId)),
|
||||
teamID: observeChannel(database, post.channelId).pipe(map((channel) => channel?.teamId)),
|
||||
currentTeamId: observeCurrentTeamId(database),
|
||||
}));
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$, combineLatest} from 'rxjs';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference';
|
||||
import {queryDisplayNamePreferences} from '@queries/servers/preference';
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
|
||||
import Opengraph from './opengraph';
|
||||
@@ -22,10 +22,10 @@ const enhance = withObservables(
|
||||
}
|
||||
|
||||
const linkPreviewsConfig = observeConfigBooleanValue(database, 'EnableLinkPreviews');
|
||||
const linkPreviewPreference = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY).
|
||||
const linkPreviewPreference = queryDisplayNamePreferences(database, Preferences.LINK_PREVIEW_DISPLAY).
|
||||
observeWithColumns(['value']);
|
||||
const showLinkPreviews = combineLatest([linkPreviewsConfig, linkPreviewPreference], (cfg, pref) => {
|
||||
const previewsEnabled = getPreferenceAsBool(pref, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY, true);
|
||||
const previewsEnabled = getDisplayNamePreferenceAsBool(pref, Preferences.LINK_PREVIEW_DISPLAY, true);
|
||||
return of$(previewsEnabled && cfg);
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {map, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {General, Permissions} from '@constants';
|
||||
import {observeChannel} from '@queries/servers/channel';
|
||||
import {observeReactionsForPost} from '@queries/servers/reaction';
|
||||
import {observePermissionForPost} from '@queries/servers/role';
|
||||
import {observeConfigBooleanValue, observeCurrentUserId} from '@queries/servers/system';
|
||||
import {observeUser} from '@queries/servers/user';
|
||||
@@ -42,7 +43,7 @@ const withReactions = withObservables(['post'], ({database, post}: WithReactions
|
||||
currentUserId,
|
||||
disabled,
|
||||
postId: of$(post.id),
|
||||
reactions: post.reactions.observe(),
|
||||
reactions: observeReactionsForPost(database, post.id),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const enhanced = withObservables(
|
||||
({database, thread}: WithDatabaseArgs & {thread: ThreadModel}) => {
|
||||
return {
|
||||
participants: queryThreadParticipants(database, thread.id).observe(),
|
||||
teamId: observeTeamIdByThread(thread),
|
||||
teamId: observeTeamIdByThread(database, thread),
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
@@ -6,12 +6,11 @@ import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {map, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {observePostAuthor, queryPostReplies} from '@queries/servers/post';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference';
|
||||
import {observePost, observePostAuthor, queryPostReplies} from '@queries/servers/post';
|
||||
import {queryDisplayNamePreferences} from '@queries/servers/preference';
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {observeTeammateNameDisplay} from '@queries/servers/user';
|
||||
import {observeTeammateNameDisplay, observeUser} from '@queries/servers/user';
|
||||
|
||||
import Header from './header';
|
||||
|
||||
@@ -26,18 +25,18 @@ type HeaderInputProps = {
|
||||
const withHeaderProps = withObservables(
|
||||
['post', 'differentThreadSequence'],
|
||||
({post, database, differentThreadSequence}: WithDatabaseArgs & HeaderInputProps) => {
|
||||
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).
|
||||
const preferences = queryDisplayNamePreferences(database).
|
||||
observeWithColumns(['value']);
|
||||
const author = observePostAuthor(database, post);
|
||||
const enablePostUsernameOverride = observeConfigBooleanValue(database, 'EnablePostUsernameOverride');
|
||||
const isTimezoneEnabled = observeConfigBooleanValue(database, 'ExperimentalTimezone');
|
||||
const isMilitaryTime = preferences.pipe(map((prefs) => getPreferenceAsBool(prefs, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)));
|
||||
const isMilitaryTime = preferences.pipe(map((prefs) => getDisplayNamePreferenceAsBool(prefs, 'use_military_time')));
|
||||
const teammateNameDisplay = observeTeammateNameDisplay(database);
|
||||
const commentCount = queryPostReplies(database, post.rootId || post.id).observeCount();
|
||||
const isCustomStatusEnabled = observeConfigBooleanValue(database, 'EnableCustomUserStatuses');
|
||||
const rootPostAuthor = differentThreadSequence ? post.root.observe().pipe(switchMap((root) => {
|
||||
if (root.length) {
|
||||
return root[0].author.observe();
|
||||
const rootPostAuthor = differentThreadSequence ? observePost(database, post.rootId).pipe(switchMap((root) => {
|
||||
if (root) {
|
||||
return observeUser(database, root.userId);
|
||||
}
|
||||
|
||||
return of$(null);
|
||||
|
||||
@@ -8,7 +8,9 @@ import {of as of$, combineLatest} from 'rxjs';
|
||||
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
|
||||
import {Permissions, Preferences, Screens} from '@constants';
|
||||
import {observePostAuthor, queryPostsBetween} from '@queries/servers/post';
|
||||
import {queryFilesForPost} from '@queries/servers/file';
|
||||
import {observePost, observePostAuthor, queryPostsBetween} from '@queries/servers/post';
|
||||
import {queryReactionsForPost} from '@queries/servers/reaction';
|
||||
import {observeCanManageChannelMembers, observePermissionForPost} from '@queries/servers/role';
|
||||
import {observeIsPostPriorityEnabled} from '@queries/servers/system';
|
||||
import {observeThreadById} from '@queries/servers/thread';
|
||||
@@ -34,7 +36,7 @@ type PropsInput = WithDatabaseArgs & {
|
||||
|
||||
function observeShouldHighlightReplyBar(database: Database, currentUser: UserModel, post: PostModel, postsInThread: PostsInThreadModel) {
|
||||
const myPostsCount = queryPostsBetween(database, postsInThread.earliest, postsInThread.latest, null, currentUser.id, '', post.rootId || post.id).observeCount();
|
||||
const root = post.root.observe().pipe(switchMap((rl) => (rl.length ? rl[0].observe() : of$(undefined))));
|
||||
const root = observePost(database, post.rootId);
|
||||
|
||||
return combineLatest([myPostsCount, root]).pipe(
|
||||
switchMap(([mpc, r]) => {
|
||||
@@ -62,14 +64,14 @@ function observeShouldHighlightReplyBar(database: Database, currentUser: UserMod
|
||||
);
|
||||
}
|
||||
|
||||
function observeHasReplies(post: PostModel) {
|
||||
function observeHasReplies(database: Database, post: PostModel) {
|
||||
if (!post.rootId) {
|
||||
return post.postsInThread.observe().pipe(switchMap((c) => of$(c.length > 0)));
|
||||
}
|
||||
|
||||
return post.root.observe().pipe(switchMap((rl) => {
|
||||
if (rl.length) {
|
||||
return rl[0].postsInThread.observe().pipe(switchMap((c) => of$(c.length > 0)));
|
||||
return observePost(database, post.rootId).pipe(switchMap((r) => {
|
||||
if (r) {
|
||||
return r.postsInThread.observe().pipe(switchMap((c) => of$(c.length > 0)));
|
||||
}
|
||||
return of$(false);
|
||||
}));
|
||||
@@ -100,7 +102,7 @@ const withPost = withObservables(
|
||||
const isEphemeral = of$(isPostEphemeral(post));
|
||||
|
||||
if (post.props?.add_channel_member && isPostEphemeral(post)) {
|
||||
isPostAddChannelMember = observeCanManageChannelMembers(database, post, currentUser);
|
||||
isPostAddChannelMember = observeCanManageChannelMembers(database, post.channelId, currentUser);
|
||||
}
|
||||
|
||||
let highlightReplyBar = of$(false);
|
||||
@@ -122,19 +124,19 @@ const withPost = withObservables(
|
||||
isLastReply = of$(!(nextPost?.rootId === post.rootId));
|
||||
}
|
||||
|
||||
const hasReplies = observeHasReplies(post);//Need to review and understand
|
||||
const hasReplies = observeHasReplies(database, post);//Need to review and understand
|
||||
|
||||
const isConsecutivePost = author.pipe(
|
||||
switchMap((user) => of$(Boolean(post && previousPost && !user?.isBot && areConsecutivePosts(post, previousPost)))),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const hasFiles = post.files.observeCount().pipe(
|
||||
const hasFiles = queryFilesForPost(database, post.id).observeCount().pipe(
|
||||
switchMap((c) => of$(c > 0)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const hasReactions = post.reactions.observeCount().pipe(
|
||||
const hasReactions = queryReactionsForPost(database, post.id).observeCount().pipe(
|
||||
switchMap((c) => of$(c > 0)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
|
||||
import {observeUser} from '@queries/servers/user';
|
||||
|
||||
import SystemMessage from './system_message';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
const withPost = withObservables(['post'], ({post}: {post: PostModel}) => ({
|
||||
author: post.author.observe(),
|
||||
const enhance = withObservables(['post'], ({post, database}: {post: PostModel} & WithDatabaseArgs) => ({
|
||||
author: observeUser(database, post.userId),
|
||||
}));
|
||||
|
||||
export default withPost(SystemMessage);
|
||||
export default withDatabase(enhance(SystemMessage));
|
||||
|
||||
@@ -6,9 +6,8 @@ import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
import {observePost, queryPostReplies} from '@queries/servers/post';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {querySavedPostsPreferences} from '@queries/servers/preference';
|
||||
|
||||
import ThreadOverview from './thread_overview';
|
||||
|
||||
@@ -19,7 +18,7 @@ const enhanced = withObservables(
|
||||
({database, rootId}: WithDatabaseArgs & {rootId: string}) => {
|
||||
return {
|
||||
rootPost: observePost(database, rootId),
|
||||
isSaved: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SAVED_POST, rootId).
|
||||
isSaved: querySavedPostsPreferences(database, rootId).
|
||||
observeWithColumns(['value']).
|
||||
pipe(
|
||||
switchMap((pref) => of$(Boolean(pref[0]?.value === 'true'))),
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {switchMap, of as of$} from 'rxjs';
|
||||
|
||||
import {observeChannel} from '@queries/servers/channel';
|
||||
import {observeTeam} from '@queries/servers/team';
|
||||
|
||||
import ChannelInfo from './channel_info';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
const enhance = withObservables(['post'], ({post}: {post: PostModel}) => {
|
||||
const channel = post.channel.observe();
|
||||
const enhance = withObservables(['post'], ({post, database}: {post: PostModel} & WithDatabaseArgs) => {
|
||||
const channel = observeChannel(database, post.channelId);
|
||||
|
||||
return {
|
||||
channelName: channel.pipe(
|
||||
switchMap((chan) => (chan ? of$(chan.displayName) : '')),
|
||||
),
|
||||
teamName: channel.pipe(
|
||||
switchMap((chan) => (chan && chan.teamId ? observeTeam(post.database, chan.teamId) : of$(null))),
|
||||
switchMap((chan) => (chan && chan.teamId ? observeTeam(database, chan.teamId) : of$(null))),
|
||||
switchMap((team) => of$(team?.displayName || null)),
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
export default enhance(ChannelInfo);
|
||||
export default withDatabase(enhance(ChannelInfo));
|
||||
|
||||
@@ -9,9 +9,8 @@ import {map} from 'rxjs/operators';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import FormattedTime from '@components/formatted_time';
|
||||
import {Preferences} from '@constants';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference';
|
||||
import {queryDisplayNamePreferences} from '@queries/servers/preference';
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
@@ -80,11 +79,11 @@ const SystemHeader = ({isMilitaryTime, isTimezoneEnabled, createAt, theme, user}
|
||||
};
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time').
|
||||
const preferences = queryDisplayNamePreferences(database, 'use_military_time').
|
||||
observeWithColumns(['value']);
|
||||
const isTimezoneEnabled = observeConfigBooleanValue(database, 'ExperimentalTimezone');
|
||||
const isMilitaryTime = preferences.pipe(
|
||||
map((prefs) => getPreferenceAsBool(prefs, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)),
|
||||
map((prefs) => getDisplayNamePreferenceAsBool(prefs, 'use_military_time')),
|
||||
);
|
||||
const user = observeCurrentUser(database);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const withTeams = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const teamIds = queryJoinedTeams(database).observe().pipe(
|
||||
map((ts) => ts.map((t) => ({id: t.id, displayName: t.displayName}))),
|
||||
);
|
||||
const order = queryPreferencesByCategoryAndName(database, Preferences.TEAMS_ORDER).
|
||||
const order = queryPreferencesByCategoryAndName(database, Preferences.CATEGORIES.TEAMS_ORDER).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap((p) => (p.length ? of$(p[0].value.split(',')) : of$([]))),
|
||||
);
|
||||
|
||||
@@ -7,8 +7,8 @@ import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import Preferences from '@constants/preferences';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {getSidebarPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {querySidebarPreferences} from '@queries/servers/preference';
|
||||
import {observeCurrentChannelId, observeCurrentTeamId, observeOnlyUnreads} from '@queries/servers/system';
|
||||
import {observeUnreadsAndMentionsInTeam} from '@queries/servers/thread';
|
||||
|
||||
@@ -22,10 +22,10 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
|
||||
return {
|
||||
currentChannelId: observeCurrentChannelId(database),
|
||||
groupUnreadsSeparately: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
|
||||
groupUnreadsSeparately: querySidebarPreferences(database, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
|
||||
observeWithColumns(['value']).
|
||||
pipe(
|
||||
switchMap((prefs: PreferenceModel[]) => of$(getPreferenceAsBool(prefs, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS, false))),
|
||||
switchMap((prefs: PreferenceModel[]) => of$(getSidebarPreferenceAsBool(prefs, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS))),
|
||||
),
|
||||
onlyUnreads: observeOnlyUnreads(database),
|
||||
unreadsAndMentions: currentTeamId.pipe(
|
||||
|
||||
@@ -928,3 +928,506 @@ exports[`components/channel_list_row should show results no tutorial 1`] = `
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`components/channel_list_row should show results no tutorial 2 users 1`] = `
|
||||
<RCTScrollView
|
||||
ListEmptyComponent={[Function]}
|
||||
ListFooterComponent={[Function]}
|
||||
contentContainerStyle={
|
||||
{
|
||||
"flexGrow": 1,
|
||||
}
|
||||
}
|
||||
data={
|
||||
[
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"auth_service": "",
|
||||
"create_at": 1111,
|
||||
"delete_at": 0,
|
||||
"email": "john@doe.com",
|
||||
"first_name": "",
|
||||
"id": "1",
|
||||
"last_name": "",
|
||||
"locale": "",
|
||||
"nickname": "",
|
||||
"notify_props": {
|
||||
"channel": "true",
|
||||
"comments": "never",
|
||||
"desktop": "mention",
|
||||
"desktop_sound": "true",
|
||||
"email": "true",
|
||||
"first_name": "true",
|
||||
"mention_keys": "",
|
||||
"push": "mention",
|
||||
"push_status": "away",
|
||||
},
|
||||
"position": "",
|
||||
"roles": "",
|
||||
"update_at": 1111,
|
||||
"username": "johndoe",
|
||||
},
|
||||
],
|
||||
"first": true,
|
||||
"id": "J",
|
||||
},
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"auth_service": "",
|
||||
"create_at": 1111,
|
||||
"delete_at": 0,
|
||||
"email": "rocky@doe.com",
|
||||
"first_name": "",
|
||||
"id": "2",
|
||||
"last_name": "",
|
||||
"locale": "",
|
||||
"nickname": "",
|
||||
"notify_props": {
|
||||
"channel": "true",
|
||||
"comments": "never",
|
||||
"desktop": "mention",
|
||||
"desktop_sound": "true",
|
||||
"email": "true",
|
||||
"first_name": "true",
|
||||
"mention_keys": "",
|
||||
"push": "mention",
|
||||
"push_status": "away",
|
||||
},
|
||||
"position": "",
|
||||
"roles": "",
|
||||
"update_at": 1111,
|
||||
"username": "rocky",
|
||||
},
|
||||
],
|
||||
"first": false,
|
||||
"id": "R",
|
||||
},
|
||||
]
|
||||
}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
initialNumToRender={15}
|
||||
keyExtractor={[Function]}
|
||||
keyboardDismissMode="on-drag"
|
||||
keyboardShouldPersistTaps="always"
|
||||
maxToRenderPerBatch={16}
|
||||
onContentSizeChange={[Function]}
|
||||
onEndReached={[Function]}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollBegin={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
removeClippedSubviews={true}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={60}
|
||||
stickyHeaderIndices={[]}
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID="UserListRow.section_list"
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "#ffffff",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "rgba(63,67,80,0.08)",
|
||||
"height": 24,
|
||||
"justifyContent": "center",
|
||||
"paddingLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"color": "#3f4350",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "600",
|
||||
"lineHeight": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
J
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
>
|
||||
<View
|
||||
accessibilityValue={
|
||||
{
|
||||
"max": undefined,
|
||||
"min": undefined,
|
||||
"now": undefined,
|
||||
"text": undefined,
|
||||
}
|
||||
}
|
||||
accessible={true}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"height": 58,
|
||||
"overflow": "hidden",
|
||||
"paddingHorizontal": 20,
|
||||
}
|
||||
}
|
||||
testID="create_direct_message.user_list.user_item.1"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"color": "#3f4350",
|
||||
"flexDirection": "row",
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"borderRadius": 21.5,
|
||||
"height": 42,
|
||||
"width": 42,
|
||||
}
|
||||
}
|
||||
testID="create_direct_message.user_list.user_item.1.profile_picture"
|
||||
>
|
||||
<Icon
|
||||
name="account-outline"
|
||||
size={24}
|
||||
style={
|
||||
{
|
||||
"color": "rgba(63,67,80,0.48)",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
[
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"justifyContent": "center",
|
||||
"paddingHorizontal": 10,
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
{
|
||||
"color": "#3f4350",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"height": 24,
|
||||
"lineHeight": 24,
|
||||
"maxWidth": "80%",
|
||||
}
|
||||
}
|
||||
testID="create_direct_message.user_list.user_item.1.display_name"
|
||||
>
|
||||
johndoe
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
color="rgba(63,67,80,0.32)"
|
||||
name="circle-outline"
|
||||
size={28}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
/>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "#ffffff",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"backgroundColor": "rgba(63,67,80,0.08)",
|
||||
"height": 24,
|
||||
"justifyContent": "center",
|
||||
"paddingLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
{
|
||||
"color": "#3f4350",
|
||||
"fontFamily": "OpenSans-SemiBold",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "600",
|
||||
"lineHeight": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
R
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
onMoveShouldSetResponder={[Function]}
|
||||
onMoveShouldSetResponderCapture={[Function]}
|
||||
onResponderEnd={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderReject={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderStart={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponderCapture={[Function]}
|
||||
>
|
||||
<View
|
||||
accessibilityValue={
|
||||
{
|
||||
"max": undefined,
|
||||
"min": undefined,
|
||||
"now": undefined,
|
||||
"text": undefined,
|
||||
}
|
||||
}
|
||||
accessible={true}
|
||||
focusable={true}
|
||||
onClick={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"height": 58,
|
||||
"overflow": "hidden",
|
||||
"paddingHorizontal": 20,
|
||||
}
|
||||
}
|
||||
testID="create_direct_message.user_list.user_item.2"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
[
|
||||
{
|
||||
"alignItems": "center",
|
||||
"color": "#3f4350",
|
||||
"flexDirection": "row",
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"borderRadius": 21.5,
|
||||
"height": 42,
|
||||
"width": 42,
|
||||
}
|
||||
}
|
||||
testID="create_direct_message.user_list.user_item.2.profile_picture"
|
||||
>
|
||||
<Icon
|
||||
name="account-outline"
|
||||
size={24}
|
||||
style={
|
||||
{
|
||||
"color": "rgba(63,67,80,0.48)",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
[
|
||||
{
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"justifyContent": "center",
|
||||
"paddingHorizontal": 10,
|
||||
},
|
||||
{
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
{
|
||||
"color": "#3f4350",
|
||||
"fontFamily": "OpenSans",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "400",
|
||||
"height": 24,
|
||||
"lineHeight": 24,
|
||||
"maxWidth": "80%",
|
||||
}
|
||||
}
|
||||
testID="create_direct_message.user_list.user_item.2.display_name"
|
||||
>
|
||||
rocky
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"justifyContent": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
color="rgba(63,67,80,0.32)"
|
||||
name="circle-outline"
|
||||
size={28}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
onFocusCapture={[Function]}
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
/>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
{
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"justifyContent": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<ActivityIndicator
|
||||
color="#1c58d9"
|
||||
size="large"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
@@ -38,6 +38,34 @@ describe('components/channel_list_row', () => {
|
||||
push_status: 'away',
|
||||
},
|
||||
};
|
||||
|
||||
const user2: UserProfile = {
|
||||
id: '2',
|
||||
create_at: 1111,
|
||||
update_at: 1111,
|
||||
delete_at: 0,
|
||||
username: 'rocky',
|
||||
auth_service: '',
|
||||
email: 'rocky@doe.com',
|
||||
nickname: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
position: '',
|
||||
roles: '',
|
||||
locale: '',
|
||||
notify_props: {
|
||||
channel: 'true',
|
||||
comments: 'never',
|
||||
desktop: 'mention',
|
||||
desktop_sound: 'true',
|
||||
email: 'true',
|
||||
first_name: 'true',
|
||||
mention_keys: '',
|
||||
push: 'mention',
|
||||
push_status: 'away',
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const server = await TestHelper.setupServerDatabase();
|
||||
database = server.database;
|
||||
@@ -91,6 +119,30 @@ describe('components/channel_list_row', () => {
|
||||
expect(wrapper.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should show results no tutorial 2 users', () => {
|
||||
const wrapper = renderWithEverything(
|
||||
<UserList
|
||||
profiles={[user, user2]}
|
||||
testID='UserListRow'
|
||||
currentUserId={'1'}
|
||||
teammateNameDisplay={'johndoe'}
|
||||
handleSelectProfile={() => {
|
||||
// noop
|
||||
}}
|
||||
fetchMore={() => {
|
||||
// noop
|
||||
}}
|
||||
loading={true}
|
||||
selectedIds={{}}
|
||||
showNoResults={true}
|
||||
tutorialWatched={true}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
|
||||
expect(wrapper.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should show results and tutorial', () => {
|
||||
const wrapper = renderWithEverything(
|
||||
<UserList
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {defineMessages, IntlShape, useIntl} from 'react-intl';
|
||||
import {FlatList, Keyboard, ListRenderItemInfo, Platform, SectionList, SectionListData, Text, View} from 'react-native';
|
||||
|
||||
import {storeProfile} from '@actions/local/user';
|
||||
@@ -13,6 +13,7 @@ import {General, Screens} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useKeyboardHeight} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import {openAsBottomSheet} from '@screens/navigation';
|
||||
import {
|
||||
changeOpacity,
|
||||
@@ -20,9 +21,23 @@ import {
|
||||
} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type UserProfileWithChannelAdmin = UserProfile & {scheme_admin?: boolean}
|
||||
type RenderItemType = ListRenderItemInfo<UserProfileWithChannelAdmin> & {section?: SectionListData<UserProfileWithChannelAdmin>}
|
||||
|
||||
const INITIAL_BATCH_TO_RENDER = 15;
|
||||
const SCROLL_EVENT_THROTTLE = 60;
|
||||
|
||||
const messages = defineMessages({
|
||||
admins: {
|
||||
id: t('mobile.manage_members.section_title_admins'),
|
||||
defaultMessage: 'CHANNEL ADMINS',
|
||||
},
|
||||
members: {
|
||||
id: t('mobile.manage_members.section_title_members'),
|
||||
defaultMessage: 'MEMBERS',
|
||||
},
|
||||
});
|
||||
|
||||
const keyboardDismissProp = Platform.select({
|
||||
android: {
|
||||
onScrollBeginDrag: Keyboard.dismiss,
|
||||
@@ -41,29 +56,58 @@ const sectionKeyExtractor = (profile: UserProfile) => {
|
||||
return profile.username[0].toUpperCase();
|
||||
};
|
||||
|
||||
export function createProfilesSections(profiles: UserProfile[]) {
|
||||
const sections: {[key: string]: UserProfile[]} = {};
|
||||
const sectionKeys: string[] = [];
|
||||
for (const profile of profiles) {
|
||||
const sectionKey = sectionKeyExtractor(profile);
|
||||
const sectionRoleKeyExtractor = (cAdmin: boolean) => {
|
||||
// Group items by channel admin or channel member
|
||||
return cAdmin ? messages.admins : messages.members;
|
||||
};
|
||||
|
||||
if (!sections[sectionKey]) {
|
||||
sections[sectionKey] = [];
|
||||
sectionKeys.push(sectionKey);
|
||||
}
|
||||
|
||||
sections[sectionKey].push(profile);
|
||||
export function createProfilesSections(intl: IntlShape, profiles: UserProfile[], members?: ChannelMember[]) {
|
||||
if (!profiles.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
sectionKeys.sort();
|
||||
const sections = new Map();
|
||||
|
||||
return sectionKeys.map((sectionKey, index) => {
|
||||
return {
|
||||
id: sectionKey,
|
||||
first: index === 0,
|
||||
data: sections[sectionKey],
|
||||
};
|
||||
});
|
||||
if (members?.length) {
|
||||
// when channel members are provided, build the sections by admins and members
|
||||
const membersDictionary = new Map();
|
||||
const membersSections = new Map();
|
||||
const {formatMessage} = intl;
|
||||
members.forEach((m) => membersDictionary.set(m.user_id, m));
|
||||
profiles.forEach((p) => {
|
||||
const member = membersDictionary.get(p.id);
|
||||
const sectionKey = sectionRoleKeyExtractor(member.scheme_admin!);
|
||||
const sectionValue = membersSections.get(sectionKey) || [];
|
||||
|
||||
// combine UserProfile and ChannelMember objects so can get channel member scheme_admin permission
|
||||
const section = [...sectionValue, {...p, ...member}];
|
||||
membersSections.set(sectionKey, section);
|
||||
});
|
||||
sections.set(formatMessage(messages.admins), membersSections.get(messages.admins));
|
||||
sections.set(formatMessage(messages.members), membersSections.get(messages.members));
|
||||
} else {
|
||||
// when channel members are not provided, build the sections alphabetically
|
||||
profiles.forEach((p) => {
|
||||
const sectionKey = sectionKeyExtractor(p);
|
||||
const sectionValue = sections.get(sectionKey) || [];
|
||||
const section = [...sectionValue, p];
|
||||
sections.set(sectionKey, section);
|
||||
});
|
||||
}
|
||||
|
||||
const results = [];
|
||||
let index = 0;
|
||||
for (const [k, v] of sections) {
|
||||
if (v) {
|
||||
results.push({
|
||||
first: index === 0,
|
||||
id: k,
|
||||
data: v,
|
||||
});
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
@@ -103,11 +147,14 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
|
||||
type Props = {
|
||||
profiles: UserProfile[];
|
||||
channelMembers?: ChannelMember[];
|
||||
currentUserId: string;
|
||||
teammateNameDisplay: string;
|
||||
handleSelectProfile: (user: UserProfile) => void;
|
||||
fetchMore: () => void;
|
||||
loading: boolean;
|
||||
manageMode?: boolean;
|
||||
showManageMode?: boolean;
|
||||
showNoResults: boolean;
|
||||
selectedIds: {[id: string]: UserProfile};
|
||||
testID?: string;
|
||||
@@ -117,12 +164,15 @@ type Props = {
|
||||
|
||||
export default function UserList({
|
||||
profiles,
|
||||
channelMembers,
|
||||
selectedIds,
|
||||
currentUserId,
|
||||
teammateNameDisplay,
|
||||
handleSelectProfile,
|
||||
fetchMore,
|
||||
loading,
|
||||
manageMode = false,
|
||||
showManageMode = false,
|
||||
showNoResults,
|
||||
term,
|
||||
testID,
|
||||
@@ -139,11 +189,16 @@ export default function UserList({
|
||||
], [style, keyboardHeight]);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (profiles.length === 0 && !loading) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (term) {
|
||||
return profiles;
|
||||
}
|
||||
return createProfilesSections(profiles);
|
||||
}, [term, profiles]);
|
||||
|
||||
return createProfilesSections(intl, profiles, channelMembers);
|
||||
}, [channelMembers, loading, profiles, term]);
|
||||
|
||||
const openUserProfile = useCallback(async (profile: UserProfile) => {
|
||||
const {user} = await storeProfile(serverUrl, profile);
|
||||
@@ -162,29 +217,34 @@ export default function UserList({
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderItem = useCallback(({item, index, section}: ListRenderItemInfo<UserProfile> & {section?: SectionListData<UserProfile>}) => {
|
||||
const renderItem = useCallback(({item, index, section}: RenderItemType) => {
|
||||
// The list will re-render when the selection changes because it's passed into the list as extraData
|
||||
const selected = Boolean(selectedIds[item.id]);
|
||||
const canAdd = Object.keys(selectedIds).length < General.MAX_USERS_IN_GM;
|
||||
|
||||
const isChAdmin = item.scheme_admin || false;
|
||||
|
||||
return (
|
||||
<UserListRow
|
||||
key={item.id}
|
||||
highlight={section?.first && index === 0}
|
||||
id={item.id}
|
||||
isChannelAdmin={isChAdmin}
|
||||
isMyUser={currentUserId === item.id}
|
||||
manageMode={manageMode}
|
||||
onPress={handleSelectProfile}
|
||||
onLongPress={openUserProfile}
|
||||
selectable={manageMode || canAdd}
|
||||
disabled={!canAdd}
|
||||
selectable={true}
|
||||
selected={selected}
|
||||
showManageMode={showManageMode}
|
||||
testID='create_direct_message.user_list.user_item'
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
tutorialWatched={tutorialWatched}
|
||||
user={item}
|
||||
/>
|
||||
);
|
||||
}, [selectedIds, currentUserId, handleSelectProfile, teammateNameDisplay, tutorialWatched]);
|
||||
}, [selectedIds, handleSelectProfile, showManageMode, manageMode, teammateNameDisplay, tutorialWatched]);
|
||||
|
||||
const renderLoading = useCallback(() => {
|
||||
if (!loading) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
|
||||
import {storeProfileLongPressTutorial} from '@actions/app/global';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
import {BotTag, GuestTag} from '@components/tag';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
@@ -19,23 +20,27 @@ import TutorialHighlight from '@components/tutorial_highlight';
|
||||
import TutorialLongPress from '@components/tutorial_highlight/long_press';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {t} from '@i18n';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {displayUsername, isGuest} from '@utils/user';
|
||||
|
||||
type Props = {
|
||||
highlight?: boolean;
|
||||
id: string;
|
||||
isMyUser: boolean;
|
||||
highlight?: boolean;
|
||||
user: UserProfile;
|
||||
teammateNameDisplay: string;
|
||||
testID: string;
|
||||
onPress?: (user: UserProfile) => void;
|
||||
isChannelAdmin: boolean;
|
||||
manageMode: boolean;
|
||||
onLongPress: (user: UserProfile) => void;
|
||||
onPress?: (user: UserProfile) => void;
|
||||
selectable: boolean;
|
||||
disabled?: boolean;
|
||||
selected: boolean;
|
||||
showManageMode: boolean;
|
||||
teammateNameDisplay: string;
|
||||
testID: string;
|
||||
tutorialWatched?: boolean;
|
||||
user: UserProfile;
|
||||
}
|
||||
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
@@ -84,6 +89,15 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
selectorManage: {
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
manageText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
...typography('Body', 100, 'Regular'),
|
||||
},
|
||||
tutorial: {
|
||||
top: Platform.select({ios: -74, default: -94}),
|
||||
},
|
||||
@@ -100,24 +114,26 @@ function UserListRow({
|
||||
id,
|
||||
isMyUser,
|
||||
highlight,
|
||||
user,
|
||||
teammateNameDisplay,
|
||||
testID,
|
||||
isChannelAdmin,
|
||||
onPress,
|
||||
onLongPress,
|
||||
tutorialWatched = false,
|
||||
manageMode = false,
|
||||
selectable,
|
||||
disabled,
|
||||
selected,
|
||||
showManageMode = false,
|
||||
teammateNameDisplay,
|
||||
testID,
|
||||
tutorialWatched = false,
|
||||
user,
|
||||
}: Props) {
|
||||
const theme = useTheme();
|
||||
const intl = useIntl();
|
||||
const isTablet = useIsTablet();
|
||||
const [showTutorial, setShowTutorial] = useState(false);
|
||||
const [itemBounds, setItemBounds] = useState<TutorialItemBounds>({startX: 0, startY: 0, endX: 0, endY: 0});
|
||||
const viewRef = useRef<View>(null);
|
||||
const style = getStyleFromTheme(theme);
|
||||
const {formatMessage} = intl;
|
||||
const {formatMessage, locale} = useIntl();
|
||||
const {username} = user;
|
||||
|
||||
const startTutorial = () => {
|
||||
@@ -152,13 +168,41 @@ function UserListRow({
|
||||
}, [highlight, tutorialWatched, isTablet]);
|
||||
|
||||
const handlePress = useCallback(() => {
|
||||
if (isMyUser && manageMode) {
|
||||
return;
|
||||
}
|
||||
onPress?.(user);
|
||||
}, [onPress, user]);
|
||||
}, [onPress, isMyUser, manageMode, user]);
|
||||
|
||||
const handleLongPress = useCallback(() => {
|
||||
onLongPress?.(user);
|
||||
}, [onLongPress, user]);
|
||||
|
||||
const manageModeIcon = useMemo(() => {
|
||||
if (!showManageMode || isMyUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const color = changeOpacity(theme.centerChannelColor, 0.64);
|
||||
const i18nId = isChannelAdmin ? t('mobile.manage_members.admin') : t('mobile.manage_members.member');
|
||||
const defaultMessage = isChannelAdmin ? 'Admin' : 'Member';
|
||||
|
||||
return (
|
||||
<View style={style.selectorManage}>
|
||||
<FormattedText
|
||||
id={i18nId}
|
||||
style={style.manageText}
|
||||
defaultMessage={defaultMessage}
|
||||
/>
|
||||
<CompassIcon
|
||||
name={'chevron-down'}
|
||||
size={18}
|
||||
color={color}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}, [isChannelAdmin, showManageMode, theme]);
|
||||
|
||||
const onLayout = useCallback(() => {
|
||||
startTutorial();
|
||||
}, []);
|
||||
@@ -189,7 +233,7 @@ function UserListRow({
|
||||
}, {username});
|
||||
}
|
||||
|
||||
const teammateDisplay = displayUsername(user, intl.locale, teammateNameDisplay);
|
||||
const teammateDisplay = displayUsername(user, locale, teammateNameDisplay);
|
||||
const showTeammateDisplay = teammateDisplay !== username;
|
||||
|
||||
const userItemTestID = `${testID}.${id}`;
|
||||
@@ -257,7 +301,7 @@ function UserListRow({
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
{icon}
|
||||
{manageMode ? manageModeIcon : icon}
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
{showTutorial &&
|
||||
@@ -267,7 +311,7 @@ function UserListRow({
|
||||
onLayout={onLayout}
|
||||
>
|
||||
<TutorialLongPress
|
||||
message={intl.formatMessage({id: 'user.tutorial.long_press', defaultMessage: "Long-press on an item to view a user's profile"})}
|
||||
message={formatMessage({id: 'user.tutorial.long_press', defaultMessage: "Long-press on an item to view a user's profile"})}
|
||||
style={isTablet ? style.tutorialTablet : style.tutorial}
|
||||
/>
|
||||
</TutorialHighlight>
|
||||
|
||||
@@ -15,6 +15,8 @@ export default keyMirror({
|
||||
LEAVE_TEAM: null,
|
||||
LOADING_CHANNEL_POSTS: null,
|
||||
NOTIFICATION_ERROR: null,
|
||||
REMOVE_USER_FROM_CHANNEL: null,
|
||||
MANAGE_USER_CHANGE_ROLE: null,
|
||||
SERVER_LOGOUT: null,
|
||||
SERVER_VERSION_CHANGED: null,
|
||||
SESSION_EXPIRED: null,
|
||||
|
||||
@@ -19,6 +19,7 @@ import Integrations from './integrations';
|
||||
import Launch from './launch';
|
||||
import License from './license';
|
||||
import List from './list';
|
||||
import Members from './members';
|
||||
import Navigation from './navigation';
|
||||
import Network from './network';
|
||||
import NotificationLevel from './notification_level';
|
||||
@@ -57,6 +58,7 @@ export {
|
||||
Launch,
|
||||
License,
|
||||
List,
|
||||
Members,
|
||||
Navigation,
|
||||
Network,
|
||||
NotificationLevel,
|
||||
|
||||
16
app/constants/members.ts
Normal file
16
app/constants/members.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import keyMirror from '@utils/key_mirror';
|
||||
const ManageOptions = keyMirror({
|
||||
REMOVE_USER: null,
|
||||
MAKE_CHANNEL_ADMIN: null,
|
||||
MAKE_CHANNEL_MEMBER: null,
|
||||
});
|
||||
|
||||
export type ManageOptionsTypes = keyof typeof ManageOptions
|
||||
|
||||
export default {
|
||||
ManageOptions,
|
||||
};
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
const Preferences: Record<string, any> = {
|
||||
CATEGORY_CHANNEL_OPEN_TIME: 'channel_open_time',
|
||||
CATEGORY_CHANNEL_APPROXIMATE_VIEW_TIME: 'channel_approximate_view_time',
|
||||
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show',
|
||||
CATEGORY_GROUP_CHANNEL_SHOW: 'group_channel_show',
|
||||
CATEGORY_EMOJI: 'emoji',
|
||||
CATEGORY_SAVED_POST: 'flagged_post',
|
||||
CATEGORY_FAVORITE_CHANNEL: 'favorite_channel',
|
||||
CATEGORY_AUTO_RESET_MANUAL_STATUS: 'auto_reset_manual_status',
|
||||
CATEGORY_NOTIFICATIONS: 'notifications',
|
||||
export const CATEGORIES_TO_KEEP: Record<string, string> = {
|
||||
ADVANCED_SETTINGS: 'advanced_settings',
|
||||
DIRECT_CHANNEL_SHOW: 'direct_channel_show',
|
||||
GROUP_CHANNEL_SHOW: 'group_channel_show',
|
||||
DISPLAY_SETTINGS: 'display_settings',
|
||||
EMOJI: 'emoji',
|
||||
NOTIFICATIONS: 'notifications',
|
||||
SAVED_POST: 'flagged_post',
|
||||
SIDEBAR_SETTINGS: 'sidebar_settings',
|
||||
TEAMS_ORDER: 'teams_order',
|
||||
THEME: 'theme',
|
||||
};
|
||||
|
||||
const CATEGORIES: Record<string, string> = {
|
||||
...CATEGORIES_TO_KEEP,
|
||||
FAVORITE_CHANNEL: 'favorite_channel',
|
||||
};
|
||||
|
||||
const Preferences = {
|
||||
CATEGORIES,
|
||||
COLLAPSED_REPLY_THREADS: 'collapsed_reply_threads',
|
||||
COLLAPSED_REPLY_THREADS_OFF: 'off',
|
||||
COLLAPSED_REPLY_THREADS_ON: 'on',
|
||||
@@ -27,7 +37,6 @@ const Preferences: Record<string, any> = {
|
||||
// "immediate" is a 30 second interval
|
||||
INTERVAL_NEVER: 0,
|
||||
INTERVAL_NOT_SET: -1,
|
||||
CATEGORY_DISPLAY_SETTINGS: 'display_settings',
|
||||
NAME_NAME_FORMAT: 'name_format',
|
||||
DISPLAY_PREFER_NICKNAME: 'nickname_full_name',
|
||||
DISPLAY_PREFER_FULL_NAME: 'full_name',
|
||||
@@ -36,18 +45,14 @@ const Preferences: Record<string, any> = {
|
||||
LINK_PREVIEW_DISPLAY: 'link_previews',
|
||||
MENTION_KEYS: 'mention_keys',
|
||||
USE_MILITARY_TIME: 'use_military_time',
|
||||
CATEGORY_SIDEBAR_SETTINGS: 'sidebar_settings',
|
||||
CHANNEL_SIDEBAR_ORGANIZATION: 'channel_sidebar_organization',
|
||||
CHANNEL_SIDEBAR_LIMIT_DMS: 'limit_visible_dms_gms',
|
||||
CHANNEL_SIDEBAR_LIMIT_DMS_DEFAULT: 20,
|
||||
CHANNEL_SIDEBAR_GROUP_UNREADS: 'show_unread_section',
|
||||
AUTOCLOSE_DMS_ENABLED: 'after_seven_days',
|
||||
CATEGORY_ADVANCED_SETTINGS: 'advanced_settings',
|
||||
ADVANCED_FILTER_JOIN_LEAVE: 'join_leave',
|
||||
ADVANCED_CODE_BLOCK_ON_CTRL_ENTER: 'code_block_ctrl_enter',
|
||||
ADVANCED_SEND_ON_CTRL_ENTER: 'send_on_ctrl_enter',
|
||||
CATEGORY_THEME: 'theme',
|
||||
TEAMS_ORDER: 'teams_order',
|
||||
THEMES: {
|
||||
denim: {
|
||||
type: 'Denim',
|
||||
|
||||
@@ -33,6 +33,7 @@ export const IN_APP_NOTIFICATION = 'InAppNotification';
|
||||
export const JOIN_TEAM = 'JoinTeam';
|
||||
export const LATEX = 'Latex';
|
||||
export const LOGIN = 'Login';
|
||||
export const MANAGE_CHANNEL_MEMBERS = 'ManageChannelMembers';
|
||||
export const MENTIONS = 'Mentions';
|
||||
export const MFA = 'MFA';
|
||||
export const ONBOARDING = 'Onboarding';
|
||||
@@ -101,6 +102,7 @@ export default {
|
||||
JOIN_TEAM,
|
||||
LATEX,
|
||||
LOGIN,
|
||||
MANAGE_CHANNEL_MEMBERS,
|
||||
MENTIONS,
|
||||
MFA,
|
||||
ONBOARDING,
|
||||
@@ -148,6 +150,7 @@ export const MODAL_SCREENS_WITHOUT_BACK = new Set<string>([
|
||||
EDIT_SERVER,
|
||||
FIND_CHANNELS,
|
||||
GALLERY,
|
||||
MANAGE_CHANNEL_MEMBERS,
|
||||
INVITE,
|
||||
PERMALINK,
|
||||
]);
|
||||
|
||||
@@ -9,6 +9,7 @@ export const SNACK_BAR_TYPE = keyMirror({
|
||||
LINK_COPIED: null,
|
||||
MESSAGE_COPIED: null,
|
||||
MUTE_CHANNEL: null,
|
||||
REMOVE_CHANNEL_USER: null,
|
||||
UNFAVORITE_CHANNEL: null,
|
||||
UNMUTE_CHANNEL: null,
|
||||
});
|
||||
@@ -45,6 +46,12 @@ export const SNACK_BAR_CONFIG: Record<string, SnackBarConfig> = {
|
||||
iconName: 'bell-off-outline',
|
||||
canUndo: true,
|
||||
},
|
||||
REMOVE_CHANNEL_USER: {
|
||||
id: t('snack.bar.remove.user'),
|
||||
defaultMessage: '1 member was removed from the channel',
|
||||
iconName: 'check',
|
||||
canUndo: true,
|
||||
},
|
||||
UNFAVORITE_CHANNEL: {
|
||||
id: t('snack.bar.unfavorite.channel'),
|
||||
defaultMessage: 'This channel was unfavorited',
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, {ComponentType, createContext, useEffect, useState} from 'react';
|
||||
import {Appearance} from 'react-native';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {queryThemePreferences} from '@queries/servers/preference';
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {setThemeDefaults, updateThemeIfNeeded} from '@utils/theme';
|
||||
|
||||
@@ -97,7 +97,7 @@ export function useTheme(): Theme {
|
||||
|
||||
const enhancedThemeProvider = withObservables([], ({database}: {database: Database}) => ({
|
||||
currentTeamId: observeCurrentTeamId(database),
|
||||
themes: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_THEME).observeWithColumns(['value']),
|
||||
themes: queryThemePreferences(database).observeWithColumns(['value']),
|
||||
}));
|
||||
|
||||
export default enhancedThemeProvider(ThemeProvider);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Database, Model, Q} from '@nozbe/watermelondb';
|
||||
import {Database, Q} from '@nozbe/watermelondb';
|
||||
import LokiJSAdapter from '@nozbe/watermelondb/adapters/lokijs';
|
||||
import logger from '@nozbe/watermelondb/utils/common/logger';
|
||||
import {DeviceEventEmitter, Platform} from 'react-native';
|
||||
@@ -45,14 +45,14 @@ class DatabaseManager {
|
||||
private readonly serverModels: Models;
|
||||
|
||||
constructor() {
|
||||
this.appModels = [InfoModel, GlobalModel, ServersModel] as unknown[] as Model[];
|
||||
this.appModels = [InfoModel, GlobalModel, ServersModel];
|
||||
this.serverModels = [
|
||||
CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, ConfigModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
GroupModel, GroupChannelModel, GroupTeamModel, GroupMembershipModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
|
||||
SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
|
||||
ThreadModel, ThreadParticipantModel, ThreadInTeamModel, TeamThreadsSyncModel, UserModel,
|
||||
] as unknown[] as Model[];
|
||||
];
|
||||
this.databaseDirectory = '';
|
||||
}
|
||||
|
||||
|
||||
@@ -43,14 +43,14 @@ class DatabaseManager {
|
||||
private readonly serverModels: Models;
|
||||
|
||||
constructor() {
|
||||
this.appModels = [InfoModel, GlobalModel, ServersModel] as unknown[] as Models;
|
||||
this.appModels = [InfoModel, GlobalModel, ServersModel];
|
||||
this.serverModels = [
|
||||
CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, ConfigModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
GroupModel, GroupChannelModel, GroupTeamModel, GroupMembershipModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
|
||||
SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
|
||||
ThreadModel, ThreadParticipantModel, ThreadInTeamModel, TeamThreadsSyncModel, UserModel,
|
||||
] as unknown[] as Models;
|
||||
];
|
||||
|
||||
this.databaseDirectory = Platform.OS === 'ios' ? getIOSAppGroupDetails().appGroupDatabase : `${FileSystem.DocumentDirectoryPath}/databases/`;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('** APP DATA OPERATOR **', () => {
|
||||
],
|
||||
tableName: 'Info',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}, 'handleInfo');
|
||||
});
|
||||
|
||||
it('=> HandleGlobal: should write to GLOBAL table', async () => {
|
||||
@@ -79,6 +79,6 @@ describe('** APP DATA OPERATOR **', () => {
|
||||
createOrUpdateRawValues: globals,
|
||||
tableName: 'Global',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}, 'handleGlobal');
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user