[CHORE] Migrate to Watermelon (#1171)
* Install * Create subscriptions * Subscription observing and sorting * Saving last message * Stash * Stash * stash * Stash * Rooms list listing :) * Animated set state * Search working * Fix load rooms on login * stash db class * set active db with path * Remove db on logout * stash * Created updateMessages * Inserting/updating threads * Persisting thread messages * Removed unused list * Loading messages from watermelon * Debounce updates and rerender message * optional fields * Fix realm conflict issues * Fix some render issues * stash * List mount * stash * fix message id * Fix tmsg * - Save subscription.rid as id on watermelon and _id as _id - Send room as param to room view * Throttle room updates * stash * comment removeClippedSubviews * Fetch thread name * try/catch updateMessages * Show loading while RoomView.init is still running * stash * Fix updateMessages * Threads * Delete message * Permalink * Pin * Star * Report * MessageActions refactor * Edit message * Reply message * Add reaction * Auto translate * Fix connection issues * Mark message as error if something happened on the call * Error actions * get custom emoji * Always run console.log when __DEV__ * Try to create serversDB * Don't call updateMessages. Execute that entire logic for one message id instead. * Refactor update messages * ServersDB User [Realm -> Watermelon] * Fix models * Custom emojis * Custom emojis on emoji picker * Frequently used emojis * Fix add reaction on message * stash * Fix * Read messages * Fix thread * Fetch thread header * Follow/unfollow thread * Fix thread * Upload file * Thread tweak * Realm -> Watermelon [Share Extension] * Add RoomsUpdatedAt to Servers Table * Settings * Settings * Fix logout * SendFileMessage ServersDB * ServersDB on serverDropdown * Remove serversDB from Realm * Load thread messages * Delete message * Improve getSettings * Improve * Remove subscription * Remove update * Update room via socket * Small refactor * Fix logout and improve migration * Refactor updateMessages * Improve migration * Remove unnecessary update * Revert remove runAfterInteractions * Fix serverDropdown * Fix merge * Init room actions Watermelon * Room actions Watermelon * Remove realm on room members * Room swipe -> Watermelon * Fix hideChannel * Get roles watermelon * Get permissions watermelon * Users typing + memory db * Auto translate watermelon * New Message View * Selected Users View * try/catch * Get Slash Commands watermelon * Slash Commands message box * Custom emojis message box * Get rooms message box * Room info view * Room info edit * Save active users * Small refactor * Message Actions * hasPermission await * last hasPermission fix * Active users on redux * Add user roles * Users typing on redux and remove memory db * Fix saga delay * Fix few issues * Fix slash commands preview * Draft message * Add muted * Unread count watermelon * Remove realm * Fiz RoomItem rerenders * Remove realm config * Rerender status update on RoomItem * Refactor RoomsListView * Fix load missed messages * Fix room update * Message refactor * Fixing lint * removeClippedSubviews on iOS only * Added few interaction managers * Fix few rerenders * Fix RoomItem status typo * Fix RoomView.SCU * Fix broadcast * Fix user status on RoomActionsView * Fix RocketChat.hasPermission * Fix database inconsistencies * Fix few update issues * Add rxjs and remove with observables * Fix tests * Remove subscriptions * Fix RoomsListView SCU * Change database structure and set all schemas to 1 * Fix RoomsListView search * Fixed errors, removed rerenders and added animation * Fixed a few errors * Fix lint * Fix issues caught by LGTM * fix ios build * Fix load unjoined channel messages * Log on database path on startup * Fix join channel * Remove react-native-realm-path * Set user status on login.user reducer * Fix status not rendering on RoomsListView * Fix few reducers * Fix users going offline * Never use "watermelon" term directly. Replaced by "database" * Fix custom emoji * Creating room from app must update roomUpdatedAt * Log subscribeRoom start * Fix room subscribe right after creating a DM * Refactor is read only on messages actions * Fix typo * Fix typo * Review * Fix schema * Fix muted & freq emoji & unpin & unstar * Remove throttleTime to room info & fix reset on edit room * Fix openServerDropdown spec & Fix unarchive * Fix MessageAction * Refactor RoomInfoEditView * Remove unnecessary condition * Remove unnecessary condition * Remove unnecessary condition * Remove get database * Rename Command.js to SlashCommand.js * Create sanitizer util * Fix indentation * Create subscription.t index * Refactor queries on RoomsListView * Create subscription.name index * Fix getPermissions * Fix indentation * Add missing await * Fix rocketchat.hasPermission * Unnecessary change * Star, pin e delete message refactored * Refactor customEmojis reducer * Remove code * Remove logs * Remove throttle * Call this.init on foreground focus on RoomView * Bump servers schema migration * Always mark message as sent after a success * Fetch only messages needed on updateMessages * Just leave a comment for now * Fetch only subscriptions returned by fetch * Set room param on RoomView header in find room * Update kotlin * Fix auto translate constructor * Fix few setState on constructor * Fix empty room image blinking while mounting * Improve fetch/persist execution for custom emojis, permissions and settings * Query only user tapped on RoomMembersView * Fix typo on canOpenRoom
This commit is contained in:
parent
2d8b1c5ac2
commit
9ba37107c7
|
@ -1,26 +0,0 @@
|
||||||
export default class Realm {
|
|
||||||
schema = [];
|
|
||||||
|
|
||||||
data = [];
|
|
||||||
|
|
||||||
constructor(params) {
|
|
||||||
require('lodash').each(params.schema, (schema) => {
|
|
||||||
this.data[schema.name] = [];
|
|
||||||
this.data[schema.name].filtered = () => this.data[schema.name];
|
|
||||||
});
|
|
||||||
this.schema = params.schema;
|
|
||||||
}
|
|
||||||
|
|
||||||
objects(schemaName) {
|
|
||||||
return this.data[schemaName];
|
|
||||||
}
|
|
||||||
|
|
||||||
write = (fn) => {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
|
|
||||||
create(schemaName, data) {
|
|
||||||
this.data[schemaName].push(data);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,5 @@
|
||||||
apply plugin: "com.android.application"
|
apply plugin: "com.android.application"
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: "io.fabric"
|
apply plugin: "io.fabric"
|
||||||
apply plugin: "com.google.firebase.firebase-perf"
|
apply plugin: "com.google.firebase.firebase-perf"
|
||||||
apply plugin: 'com.bugsnag.android.gradle'
|
apply plugin: 'com.bugsnag.android.gradle'
|
||||||
|
@ -204,6 +205,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
addUnimodulesDependencies()
|
addUnimodulesDependencies()
|
||||||
|
implementation project(':watermelondb')
|
||||||
implementation project(':reactnativenotifications')
|
implementation project(':reactnativenotifications')
|
||||||
implementation project(":reactnativekeyboardinput")
|
implementation project(":reactnativekeyboardinput")
|
||||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
|
|
|
@ -31,6 +31,8 @@ import io.invertase.firebase.fabric.crashlytics.RNFirebaseCrashlyticsPackage;
|
||||||
import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage;
|
import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage;
|
||||||
import io.invertase.firebase.perf.RNFirebasePerformancePackage;
|
import io.invertase.firebase.perf.RNFirebasePerformancePackage;
|
||||||
|
|
||||||
|
import com.nozbe.watermelondb.WatermelonDBPackage;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -53,6 +55,7 @@ public class MainApplication extends Application implements ReactApplication, IN
|
||||||
packages.add(new RNFirebasePerformancePackage());
|
packages.add(new RNFirebasePerformancePackage());
|
||||||
packages.add(new KeyboardInputPackage(MainApplication.this));
|
packages.add(new KeyboardInputPackage(MainApplication.this));
|
||||||
packages.add(new RNNotificationsPackage(MainApplication.this));
|
packages.add(new RNNotificationsPackage(MainApplication.this));
|
||||||
|
packages.add(new WatermelonDBPackage());
|
||||||
packages.add(new ModuleRegistryAdapter(mModuleRegistryProvider));
|
packages.add(new ModuleRegistryAdapter(mModuleRegistryProvider));
|
||||||
return packages;
|
return packages;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,7 @@ buildscript {
|
||||||
compileSdkVersion = 28
|
compileSdkVersion = 28
|
||||||
targetSdkVersion = 28
|
targetSdkVersion = 28
|
||||||
glideVersion = "4.9.0"
|
glideVersion = "4.9.0"
|
||||||
// googlePlayServicesVersion = "17.0.0"
|
kotlin_version = "1.3.50"
|
||||||
// supportLibVersion = "1.0.2"
|
|
||||||
// mediaCompatVersion = '1.0.1'
|
|
||||||
// supportV4Version = '1.0.0'
|
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
@ -24,6 +21,7 @@ buildscript {
|
||||||
classpath 'com.google.gms:google-services:4.2.0'
|
classpath 'com.google.gms:google-services:4.2.0'
|
||||||
classpath 'io.fabric.tools:gradle:1.28.1'
|
classpath 'io.fabric.tools:gradle:1.28.1'
|
||||||
classpath 'com.google.firebase:perf-plugin:1.2.1'
|
classpath 'com.google.firebase:perf-plugin:1.2.1'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.+'
|
classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.+'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
|
|
@ -2,6 +2,8 @@ apply from: '../node_modules/react-native-unimodules/gradle.groovy'
|
||||||
includeUnimodulesProjects()
|
includeUnimodulesProjects()
|
||||||
|
|
||||||
rootProject.name = 'RocketChatRN'
|
rootProject.name = 'RocketChatRN'
|
||||||
|
include ':watermelondb'
|
||||||
|
project(':watermelondb').projectDir = new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android')
|
||||||
include ':reactnativenotifications'
|
include ':reactnativenotifications'
|
||||||
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android/app')
|
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android/app')
|
||||||
include ':reactnativekeyboardinput'
|
include ':reactnativekeyboardinput'
|
||||||
|
|
|
@ -32,31 +32,7 @@ export const ROOMS = createRequestTypes('ROOMS', [
|
||||||
]);
|
]);
|
||||||
export const ROOM = createRequestTypes('ROOM', ['LEAVE', 'ERASE', 'USER_TYPING']);
|
export const ROOM = createRequestTypes('ROOM', ['LEAVE', 'ERASE', 'USER_TYPING']);
|
||||||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT']);
|
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT']);
|
||||||
export const MESSAGES = createRequestTypes('MESSAGES', [
|
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
||||||
...defaultTypes,
|
|
||||||
'ACTIONS_SHOW',
|
|
||||||
'ACTIONS_HIDE',
|
|
||||||
'ERROR_ACTIONS_SHOW',
|
|
||||||
'ERROR_ACTIONS_HIDE',
|
|
||||||
'DELETE_REQUEST',
|
|
||||||
'DELETE_SUCCESS',
|
|
||||||
'DELETE_FAILURE',
|
|
||||||
'EDIT_INIT',
|
|
||||||
'EDIT_CANCEL',
|
|
||||||
'EDIT_REQUEST',
|
|
||||||
'EDIT_SUCCESS',
|
|
||||||
'EDIT_FAILURE',
|
|
||||||
'TOGGLE_STAR_REQUEST',
|
|
||||||
'TOGGLE_STAR_SUCCESS',
|
|
||||||
'TOGGLE_STAR_FAILURE',
|
|
||||||
'TOGGLE_PIN_REQUEST',
|
|
||||||
'TOGGLE_PIN_SUCCESS',
|
|
||||||
'TOGGLE_PIN_FAILURE',
|
|
||||||
'REPLY_INIT',
|
|
||||||
'REPLY_CANCEL',
|
|
||||||
'TOGGLE_REACTION_PICKER',
|
|
||||||
'REPLY_BROADCAST'
|
|
||||||
]);
|
|
||||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||||
export const SELECTED_USERS = createRequestTypes('SELECTED_USERS', ['ADD_USER', 'REMOVE_USER', 'RESET', 'SET_LOADING']);
|
export const SELECTED_USERS = createRequestTypes('SELECTED_USERS', ['ADD_USER', 'REMOVE_USER', 'RESET', 'SET_LOADING']);
|
||||||
export const SERVER = createRequestTypes('SERVER', [
|
export const SERVER = createRequestTypes('SERVER', [
|
||||||
|
@ -75,3 +51,6 @@ export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL
|
||||||
export const NOTIFICATION = createRequestTypes('NOTIFICATION', ['RECEIVED', 'REMOVE']);
|
export const NOTIFICATION = createRequestTypes('NOTIFICATION', ['RECEIVED', 'REMOVE']);
|
||||||
export const TOGGLE_MARKDOWN = 'TOGGLE_MARKDOWN';
|
export const TOGGLE_MARKDOWN = 'TOGGLE_MARKDOWN';
|
||||||
export const TOGGLE_CRASH_REPORT = 'TOGGLE_CRASH_REPORT';
|
export const TOGGLE_CRASH_REPORT = 'TOGGLE_CRASH_REPORT';
|
||||||
|
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
|
||||||
|
export const SET_ACTIVE_USERS = 'SET_ACTIVE_USERS';
|
||||||
|
export const USERS_TYPING = createRequestTypes('USERS_TYPING', ['ADD', 'REMOVE', 'CLEAR']);
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { SET_ACTIVE_USERS } from './actionsTypes';
|
||||||
|
|
||||||
|
export function setActiveUsers(activeUsers) {
|
||||||
|
return {
|
||||||
|
type: SET_ACTIVE_USERS,
|
||||||
|
activeUsers
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
|
export function setCustomEmojis(emojis) {
|
||||||
|
return {
|
||||||
|
type: types.SET_CUSTOM_EMOJIS,
|
||||||
|
emojis
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,143 +1,5 @@
|
||||||
import * as types from './actionsTypes';
|
import * as types from './actionsTypes';
|
||||||
|
|
||||||
export function actionsShow(actionMessage) {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.ACTIONS_SHOW,
|
|
||||||
actionMessage
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function actionsHide() {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.ACTIONS_HIDE
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function errorActionsShow(actionMessage) {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.ERROR_ACTIONS_SHOW,
|
|
||||||
actionMessage
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function errorActionsHide() {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.ERROR_ACTIONS_HIDE
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteRequest(message) {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.DELETE_REQUEST,
|
|
||||||
message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteSuccess() {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.DELETE_SUCCESS
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteFailure() {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.DELETE_FAILURE
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function editInit(message) {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.EDIT_INIT,
|
|
||||||
message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function editCancel() {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.EDIT_CANCEL
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function editRequest(message) {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.EDIT_REQUEST,
|
|
||||||
message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function editSuccess() {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.EDIT_SUCCESS
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function editFailure() {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.EDIT_FAILURE
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleStarRequest(message) {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.TOGGLE_STAR_REQUEST,
|
|
||||||
message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleStarSuccess() {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.TOGGLE_STAR_SUCCESS
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleStarFailure() {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.TOGGLE_STAR_FAILURE
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function togglePinRequest(message) {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.TOGGLE_PIN_REQUEST,
|
|
||||||
message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function togglePinSuccess() {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.TOGGLE_PIN_SUCCESS
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function togglePinFailure(err) {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.TOGGLE_PIN_FAILURE,
|
|
||||||
err
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function replyInit(message, mention) {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.REPLY_INIT,
|
|
||||||
message,
|
|
||||||
mention
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function replyCancel() {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.REPLY_CANCEL
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleReactionPicker(message) {
|
|
||||||
return {
|
|
||||||
type: types.MESSAGES.TOGGLE_REACTION_PICKER,
|
|
||||||
message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function replyBroadcast(message) {
|
export function replyBroadcast(message) {
|
||||||
return {
|
return {
|
||||||
type: types.MESSAGES.REPLY_BROADCAST,
|
type: types.MESSAGES.REPLY_BROADCAST,
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { USERS_TYPING } from './actionsTypes';
|
||||||
|
|
||||||
|
export function addUserTyping(username) {
|
||||||
|
return {
|
||||||
|
type: USERS_TYPING.ADD,
|
||||||
|
username
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeUserTyping(username) {
|
||||||
|
return {
|
||||||
|
type: USERS_TYPING.REMOVE,
|
||||||
|
username
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearUserTyping() {
|
||||||
|
return {
|
||||||
|
type: USERS_TYPING.CLEAR
|
||||||
|
};
|
||||||
|
}
|
|
@ -2,26 +2,30 @@ import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ScrollView } from 'react-native';
|
import { ScrollView } from 'react-native';
|
||||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||||
import map from 'lodash/map';
|
|
||||||
import { emojify } from 'react-emojione';
|
import { emojify } from 'react-emojione';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import orderBy from 'lodash/orderBy';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
|
||||||
import TabBar from './TabBar';
|
import TabBar from './TabBar';
|
||||||
import EmojiCategory from './EmojiCategory';
|
import EmojiCategory from './EmojiCategory';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import categories from './categories';
|
import categories from './categories';
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
import database from '../../lib/database';
|
||||||
import { emojisByCategory } from '../../emojis';
|
import { emojisByCategory } from '../../emojis';
|
||||||
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
||||||
|
import log from '../../utils/log';
|
||||||
|
|
||||||
const scrollProps = {
|
const scrollProps = {
|
||||||
keyboardShouldPersistTaps: 'always',
|
keyboardShouldPersistTaps: 'always',
|
||||||
keyboardDismissMode: 'none'
|
keyboardDismissMode: 'none'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class EmojiPicker extends Component {
|
class EmojiPicker extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
baseUrl: PropTypes.string.isRequired,
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
customEmojis: PropTypes.object,
|
||||||
onEmojiSelected: PropTypes.func,
|
onEmojiSelected: PropTypes.func,
|
||||||
tabEmojiStyle: PropTypes.object,
|
tabEmojiStyle: PropTypes.object,
|
||||||
emojisPerRow: PropTypes.number,
|
emojisPerRow: PropTypes.number,
|
||||||
|
@ -30,27 +34,27 @@ export default class EmojiPicker extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.frequentlyUsed = database.objects('frequentlyUsedEmoji').sorted('count', true);
|
const customEmojis = Object.keys(props.customEmojis)
|
||||||
this.customEmojis = database.objects('customEmojis');
|
.filter(item => item === props.customEmojis[item].name)
|
||||||
|
.map(item => ({
|
||||||
|
content: props.customEmojis[item].name,
|
||||||
|
extension: props.customEmojis[item].extension,
|
||||||
|
isCustom: true
|
||||||
|
}));
|
||||||
this.state = {
|
this.state = {
|
||||||
frequentlyUsed: [],
|
frequentlyUsed: [],
|
||||||
customEmojis: [],
|
customEmojis,
|
||||||
show: false
|
show: false
|
||||||
};
|
};
|
||||||
this.updateFrequentlyUsed = this.updateFrequentlyUsed.bind(this);
|
|
||||||
this.updateCustomEmojis = this.updateCustomEmojis.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
async componentDidMount() {
|
||||||
this.updateFrequentlyUsed();
|
await this.updateFrequentlyUsed();
|
||||||
this.updateCustomEmojis();
|
this.setState({ show: true });
|
||||||
requestAnimationFrame(() => this.setState({ show: true }));
|
|
||||||
safeAddListener(this.frequentlyUsed, this.updateFrequentlyUsed);
|
|
||||||
safeAddListener(this.customEmojis, this.updateCustomEmojis);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const { frequentlyUsed, customEmojis, show } = this.state;
|
const { frequentlyUsed, show } = this.state;
|
||||||
const { width } = this.props;
|
const { width } = this.props;
|
||||||
if (nextState.show !== show) {
|
if (nextState.show !== show) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -61,64 +65,70 @@ export default class EmojiPicker extends Component {
|
||||||
if (!equal(nextState.frequentlyUsed, frequentlyUsed)) {
|
if (!equal(nextState.frequentlyUsed, frequentlyUsed)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!equal(nextState.customEmojis, customEmojis)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
onEmojiSelected = (emoji) => {
|
||||||
this.frequentlyUsed.removeAllListeners();
|
try {
|
||||||
this.customEmojis.removeAllListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
onEmojiSelected(emoji) {
|
|
||||||
const { onEmojiSelected } = this.props;
|
const { onEmojiSelected } = this.props;
|
||||||
if (emoji.isCustom) {
|
if (emoji.isCustom) {
|
||||||
const count = this._getFrequentlyUsedCount(emoji.content);
|
|
||||||
this._addFrequentlyUsed({
|
this._addFrequentlyUsed({
|
||||||
content: emoji.content, extension: emoji.extension, count, isCustom: true
|
content: emoji.content, extension: emoji.extension, isCustom: true
|
||||||
});
|
});
|
||||||
onEmojiSelected(`:${ emoji.content }:`);
|
onEmojiSelected(`:${ emoji.content }:`);
|
||||||
} else {
|
} else {
|
||||||
const content = emoji;
|
const content = emoji;
|
||||||
const count = this._getFrequentlyUsedCount(content);
|
this._addFrequentlyUsed({ content, isCustom: false });
|
||||||
this._addFrequentlyUsed({ content, count, isCustom: false });
|
|
||||||
const shortname = `:${ emoji }:`;
|
const shortname = `:${ emoji }:`;
|
||||||
onEmojiSelected(emojify(shortname, { output: 'unicode' }), shortname);
|
onEmojiSelected(emojify(shortname, { output: 'unicode' }), shortname);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
_addFrequentlyUsed = protectedFunction((emoji) => {
|
_addFrequentlyUsed = protectedFunction(async(emoji) => {
|
||||||
database.write(() => {
|
const db = database.active;
|
||||||
database.create('frequentlyUsedEmoji', emoji, true);
|
const freqEmojiCollection = db.collections.get('frequently_used_emojis');
|
||||||
|
await db.action(async() => {
|
||||||
|
try {
|
||||||
|
const freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
|
||||||
|
await freqEmojiRecord.update((f) => {
|
||||||
|
f.count += 1;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
await freqEmojiCollection.create((f) => {
|
||||||
|
f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema);
|
||||||
|
Object.assign(f, emoji);
|
||||||
|
f.count = 1;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
_getFrequentlyUsedCount = (content) => {
|
updateFrequentlyUsed = async() => {
|
||||||
const emojiRow = this.frequentlyUsed.filtered('content == $0', content);
|
const db = database.active;
|
||||||
return emojiRow.length ? emojiRow[0].count + 1 : 1;
|
const frequentlyUsedRecords = await db.collections.get('frequently_used_emojis').query().fetch();
|
||||||
}
|
let frequentlyUsed = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
|
||||||
|
frequentlyUsed = frequentlyUsed.map((item) => {
|
||||||
updateFrequentlyUsed() {
|
|
||||||
const frequentlyUsed = map(this.frequentlyUsed.slice(), (item) => {
|
|
||||||
if (item.isCustom) {
|
if (item.isCustom) {
|
||||||
return item;
|
return { content: item.content, extension: item.extension, isCustom: item.isCustom };
|
||||||
}
|
}
|
||||||
return emojify(`${ item.content }`, { output: 'unicode' });
|
return emojify(`${ item.content }`, { output: 'unicode' });
|
||||||
});
|
});
|
||||||
this.setState({ frequentlyUsed });
|
this.setState({ frequentlyUsed });
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCustomEmojis() {
|
|
||||||
const customEmojis = map(this.customEmojis.slice(), item => ({ content: item.name, extension: item.extension, isCustom: true }));
|
|
||||||
this.setState({ customEmojis });
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCategory(category, i) {
|
renderCategory(category, i) {
|
||||||
const { frequentlyUsed, customEmojis } = this.state;
|
const { frequentlyUsed, customEmojis } = this.state;
|
||||||
const { emojisPerRow, width, baseUrl } = this.props;
|
const {
|
||||||
|
emojisPerRow, width, baseUrl
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
let emojis = [];
|
let emojis = [];
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
|
@ -171,3 +181,9 @@ export default class EmojiPicker extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
customEmojis: state.customEmojis
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(EmojiPicker);
|
||||||
|
|
|
@ -6,17 +6,8 @@ import ActionSheet from 'react-native-action-sheet';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
|
|
||||||
import {
|
|
||||||
actionsHide as actionsHideAction,
|
|
||||||
deleteRequest as deleteRequestAction,
|
|
||||||
editInit as editInitAction,
|
|
||||||
replyInit as replyInitAction,
|
|
||||||
togglePinRequest as togglePinRequestAction,
|
|
||||||
toggleReactionPicker as toggleReactionPickerAction,
|
|
||||||
toggleStarRequest as toggleStarRequestAction
|
|
||||||
} from '../actions/messages';
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/database';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
|
@ -28,14 +19,12 @@ class MessageActions extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
actionsHide: PropTypes.func.isRequired,
|
actionsHide: PropTypes.func.isRequired,
|
||||||
room: PropTypes.object.isRequired,
|
room: PropTypes.object.isRequired,
|
||||||
actionMessage: PropTypes.object,
|
message: PropTypes.object,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
deleteRequest: PropTypes.func.isRequired,
|
|
||||||
editInit: PropTypes.func.isRequired,
|
editInit: PropTypes.func.isRequired,
|
||||||
toggleStarRequest: PropTypes.func.isRequired,
|
reactionInit: PropTypes.func.isRequired,
|
||||||
togglePinRequest: PropTypes.func.isRequired,
|
|
||||||
toggleReactionPicker: PropTypes.func.isRequired,
|
|
||||||
replyInit: PropTypes.func.isRequired,
|
replyInit: PropTypes.func.isRequired,
|
||||||
|
isReadOnly: PropTypes.bool,
|
||||||
Message_AllowDeleting: PropTypes.bool,
|
Message_AllowDeleting: PropTypes.bool,
|
||||||
Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number,
|
Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number,
|
||||||
Message_AllowEditing: PropTypes.bool,
|
Message_AllowEditing: PropTypes.bool,
|
||||||
|
@ -48,22 +37,27 @@ class MessageActions extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleActionPress = this.handleActionPress.bind(this);
|
this.handleActionPress = this.handleActionPress.bind(this);
|
||||||
this.setPermissions();
|
}
|
||||||
|
|
||||||
const { Message_AllowStarring, Message_AllowPinning, Message_Read_Receipt_Store_Users } = this.props;
|
async componentDidMount() {
|
||||||
|
await this.setPermissions();
|
||||||
|
|
||||||
|
const {
|
||||||
|
Message_AllowStarring, Message_AllowPinning, Message_Read_Receipt_Store_Users, user, room, message, isReadOnly
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
// Cancel
|
// Cancel
|
||||||
this.options = [I18n.t('Cancel')];
|
this.options = [I18n.t('Cancel')];
|
||||||
this.CANCEL_INDEX = 0;
|
this.CANCEL_INDEX = 0;
|
||||||
|
|
||||||
// Reply
|
// Reply
|
||||||
if (!this.isRoomReadOnly()) {
|
if (!isReadOnly) {
|
||||||
this.options.push(I18n.t('Reply'));
|
this.options.push(I18n.t('Reply'));
|
||||||
this.REPLY_INDEX = this.options.length - 1;
|
this.REPLY_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edit
|
// Edit
|
||||||
if (this.allowEdit(props)) {
|
if (this.allowEdit(this.props)) {
|
||||||
this.options.push(I18n.t('Edit'));
|
this.options.push(I18n.t('Edit'));
|
||||||
this.EDIT_INDEX = this.options.length - 1;
|
this.EDIT_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
@ -81,25 +75,25 @@ class MessageActions extends React.Component {
|
||||||
this.SHARE_INDEX = this.options.length - 1;
|
this.SHARE_INDEX = this.options.length - 1;
|
||||||
|
|
||||||
// Quote
|
// Quote
|
||||||
if (!this.isRoomReadOnly()) {
|
if (!isReadOnly) {
|
||||||
this.options.push(I18n.t('Quote'));
|
this.options.push(I18n.t('Quote'));
|
||||||
this.QUOTE_INDEX = this.options.length - 1;
|
this.QUOTE_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Star
|
// Star
|
||||||
if (Message_AllowStarring) {
|
if (Message_AllowStarring) {
|
||||||
this.options.push(I18n.t(props.actionMessage.starred ? 'Unstar' : 'Star'));
|
this.options.push(I18n.t(message.starred ? 'Unstar' : 'Star'));
|
||||||
this.STAR_INDEX = this.options.length - 1;
|
this.STAR_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pin
|
// Pin
|
||||||
if (Message_AllowPinning) {
|
if (Message_AllowPinning) {
|
||||||
this.options.push(I18n.t(props.actionMessage.pinned ? 'Unpin' : 'Pin'));
|
this.options.push(I18n.t(message.pinned ? 'Unpin' : 'Pin'));
|
||||||
this.PIN_INDEX = this.options.length - 1;
|
this.PIN_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reaction
|
// Reaction
|
||||||
if (!this.isRoomReadOnly() || this.canReactWhenReadOnly()) {
|
if (!isReadOnly || this.canReactWhenReadOnly()) {
|
||||||
this.options.push(I18n.t('Add_Reaction'));
|
this.options.push(I18n.t('Add_Reaction'));
|
||||||
this.REACTION_INDEX = this.options.length - 1;
|
this.REACTION_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
@ -111,8 +105,8 @@ class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle Auto-translate
|
// Toggle Auto-translate
|
||||||
if (props.room.autoTranslate && props.actionMessage.u && props.actionMessage.u._id !== props.user.id) {
|
if (room.autoTranslate && message.u && message.u._id !== user.id) {
|
||||||
this.options.push(I18n.t(props.actionMessage.autoTranslate ? 'View_Original' : 'Translate'));
|
this.options.push(I18n.t(message.autoTranslate ? 'View_Original' : 'Translate'));
|
||||||
this.TOGGLE_TRANSLATION_INDEX = this.options.length - 1;
|
this.TOGGLE_TRANSLATION_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +115,7 @@ class MessageActions extends React.Component {
|
||||||
this.REPORT_INDEX = this.options.length - 1;
|
this.REPORT_INDEX = this.options.length - 1;
|
||||||
|
|
||||||
// Delete
|
// Delete
|
||||||
if (this.allowDelete(props)) {
|
if (this.allowDelete(this.props)) {
|
||||||
this.options.push(I18n.t('Delete'));
|
this.options.push(I18n.t('Delete'));
|
||||||
this.DELETE_INDEX = this.options.length - 1;
|
this.DELETE_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
@ -131,13 +125,18 @@ class MessageActions extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setPermissions() {
|
async setPermissions() {
|
||||||
|
try {
|
||||||
const { room } = this.props;
|
const { room } = this.props;
|
||||||
const permissions = ['edit-message', 'delete-message', 'force-delete-message'];
|
const permissions = ['edit-message', 'delete-message', 'force-delete-message'];
|
||||||
const result = RocketChat.hasPermission(permissions, room.rid);
|
const result = await RocketChat.hasPermission(permissions, room.rid);
|
||||||
this.hasEditPermission = result[permissions[0]];
|
this.hasEditPermission = result[permissions[0]];
|
||||||
this.hasDeletePermission = result[permissions[1]];
|
this.hasDeletePermission = result[permissions[1]];
|
||||||
this.hasForceDeletePermission = result[permissions[2]];
|
this.hasForceDeletePermission = result[permissions[2]];
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
showActionSheet = () => {
|
showActionSheet = () => {
|
||||||
|
@ -159,12 +158,7 @@ class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isOwn = props => props.actionMessage.u && props.actionMessage.u._id === props.user.id;
|
isOwn = props => props.message.u && props.message.u._id === props.user.id;
|
||||||
|
|
||||||
isRoomReadOnly = () => {
|
|
||||||
const { room } = this.props;
|
|
||||||
return room.ro;
|
|
||||||
}
|
|
||||||
|
|
||||||
canReactWhenReadOnly = () => {
|
canReactWhenReadOnly = () => {
|
||||||
const { room } = this.props;
|
const { room } = this.props;
|
||||||
|
@ -172,7 +166,7 @@ class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
allowEdit = (props) => {
|
allowEdit = (props) => {
|
||||||
if (this.isRoomReadOnly()) {
|
if (props.isReadOnly) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const editOwn = this.isOwn(props);
|
const editOwn = this.isOwn(props);
|
||||||
|
@ -184,8 +178,8 @@ class MessageActions extends React.Component {
|
||||||
const blockEditInMinutes = Message_AllowEditing_BlockEditInMinutes;
|
const blockEditInMinutes = Message_AllowEditing_BlockEditInMinutes;
|
||||||
if (blockEditInMinutes) {
|
if (blockEditInMinutes) {
|
||||||
let msgTs;
|
let msgTs;
|
||||||
if (props.actionMessage.ts != null) {
|
if (props.message.ts != null) {
|
||||||
msgTs = moment(props.actionMessage.ts);
|
msgTs = moment(props.message.ts);
|
||||||
}
|
}
|
||||||
let currentTsDiff;
|
let currentTsDiff;
|
||||||
if (msgTs != null) {
|
if (msgTs != null) {
|
||||||
|
@ -197,12 +191,12 @@ class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
allowDelete = (props) => {
|
allowDelete = (props) => {
|
||||||
if (this.isRoomReadOnly()) {
|
if (props.isReadOnly) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent from deleting thread start message when positioned inside the thread
|
// Prevent from deleting thread start message when positioned inside the thread
|
||||||
if (props.tmid && props.tmid === props.actionMessage._id) {
|
if (props.tmid && props.tmid === props.message.id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const deleteOwn = this.isOwn(props);
|
const deleteOwn = this.isOwn(props);
|
||||||
|
@ -216,8 +210,8 @@ class MessageActions extends React.Component {
|
||||||
const blockDeleteInMinutes = Message_AllowDeleting_BlockDeleteInMinutes;
|
const blockDeleteInMinutes = Message_AllowDeleting_BlockDeleteInMinutes;
|
||||||
if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) {
|
if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) {
|
||||||
let msgTs;
|
let msgTs;
|
||||||
if (props.actionMessage.ts != null) {
|
if (props.message.ts != null) {
|
||||||
msgTs = moment(props.actionMessage.ts);
|
msgTs = moment(props.message.ts);
|
||||||
}
|
}
|
||||||
let currentTsDiff;
|
let currentTsDiff;
|
||||||
if (msgTs != null) {
|
if (msgTs != null) {
|
||||||
|
@ -229,7 +223,7 @@ class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDelete = () => {
|
handleDelete = () => {
|
||||||
const { deleteRequest, actionMessage } = this.props;
|
const { message } = this.props;
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
I18n.t('Are_you_sure_question_mark'),
|
I18n.t('Are_you_sure_question_mark'),
|
||||||
I18n.t('You_will_not_be_able_to_recover_this_message'),
|
I18n.t('You_will_not_be_able_to_recover_this_message'),
|
||||||
|
@ -241,7 +235,13 @@ class MessageActions extends React.Component {
|
||||||
{
|
{
|
||||||
text: I18n.t('Yes_action_it', { action: 'delete' }),
|
text: I18n.t('Yes_action_it', { action: 'delete' }),
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: () => deleteRequest(actionMessage)
|
onPress: async() => {
|
||||||
|
try {
|
||||||
|
await RocketChat.deleteMessage(message.id, message.rid);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
{ cancelable: false }
|
{ cancelable: false }
|
||||||
|
@ -249,66 +249,73 @@ class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEdit = () => {
|
handleEdit = () => {
|
||||||
const { actionMessage, editInit } = this.props;
|
const { message, editInit } = this.props;
|
||||||
const { _id, msg, rid } = actionMessage;
|
editInit(message);
|
||||||
editInit({ _id, msg, rid });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCopy = async() => {
|
handleCopy = async() => {
|
||||||
const { actionMessage } = this.props;
|
const { message } = this.props;
|
||||||
await Clipboard.setString(actionMessage.msg);
|
await Clipboard.setString(message.msg);
|
||||||
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleShare = async() => {
|
handleShare = async() => {
|
||||||
const { actionMessage } = this.props;
|
const { message } = this.props;
|
||||||
const permalink = await this.getPermalink(actionMessage);
|
const permalink = await this.getPermalink(message);
|
||||||
Share.share({
|
Share.share({
|
||||||
message: permalink
|
message: permalink
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleStar = () => {
|
handleStar = async() => {
|
||||||
const { actionMessage, toggleStarRequest } = this.props;
|
const { message } = this.props;
|
||||||
toggleStarRequest(actionMessage);
|
try {
|
||||||
|
await RocketChat.toggleStarMessage(message.id, message.starred);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePermalink = async() => {
|
handlePermalink = async() => {
|
||||||
const { actionMessage } = this.props;
|
const { message } = this.props;
|
||||||
const permalink = await this.getPermalink(actionMessage);
|
const permalink = await this.getPermalink(message);
|
||||||
Clipboard.setString(permalink);
|
Clipboard.setString(permalink);
|
||||||
EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') });
|
EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') });
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePin = () => {
|
handlePin = async() => {
|
||||||
const { actionMessage, togglePinRequest } = this.props;
|
const { message } = this.props;
|
||||||
togglePinRequest(actionMessage);
|
try {
|
||||||
|
await RocketChat.togglePinMessage(message.id, message.pinned);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReply = () => {
|
handleReply = () => {
|
||||||
const { actionMessage, replyInit } = this.props;
|
const { message, replyInit } = this.props;
|
||||||
replyInit(actionMessage, true);
|
replyInit(message, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQuote = () => {
|
handleQuote = () => {
|
||||||
const { actionMessage, replyInit } = this.props;
|
const { message, replyInit } = this.props;
|
||||||
replyInit(actionMessage, false);
|
replyInit(message, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReaction = () => {
|
handleReaction = () => {
|
||||||
const { actionMessage, toggleReactionPicker } = this.props;
|
const { message, reactionInit } = this.props;
|
||||||
toggleReactionPicker(actionMessage);
|
reactionInit(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReadReceipt = () => {
|
handleReadReceipt = () => {
|
||||||
const { actionMessage } = this.props;
|
const { message } = this.props;
|
||||||
Navigation.navigate('ReadReceiptsView', { messageId: actionMessage._id });
|
Navigation.navigate('ReadReceiptsView', { messageId: message.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReport = async() => {
|
handleReport = async() => {
|
||||||
const { actionMessage } = this.props;
|
const { message } = this.props;
|
||||||
try {
|
try {
|
||||||
await RocketChat.reportMessage(actionMessage._id);
|
await RocketChat.reportMessage(message.id);
|
||||||
Alert.alert(I18n.t('Message_Reported'));
|
Alert.alert(I18n.t('Message_Reported'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
|
@ -316,16 +323,24 @@ class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleToggleTranslation = async() => {
|
handleToggleTranslation = async() => {
|
||||||
const { actionMessage, room } = this.props;
|
const { message, room } = this.props;
|
||||||
try {
|
try {
|
||||||
const message = database.objectForPrimaryKey('messages', actionMessage._id);
|
const db = database.active;
|
||||||
database.write(() => {
|
await db.action(async() => {
|
||||||
message.autoTranslate = !message.autoTranslate;
|
await message.update((m) => {
|
||||||
message._updatedAt = new Date();
|
m.autoTranslate = !m.autoTranslate;
|
||||||
|
m._updatedAt = new Date();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
const translatedMessage = getMessageTranslation(message, room.autoTranslateLanguage);
|
const translatedMessage = getMessageTranslation(message, room.autoTranslateLanguage);
|
||||||
if (!translatedMessage) {
|
if (!translatedMessage) {
|
||||||
await RocketChat.translateMessage(actionMessage, room.autoTranslateLanguage);
|
const m = {
|
||||||
|
_id: message.id,
|
||||||
|
rid: message.subscription.id,
|
||||||
|
u: message.u,
|
||||||
|
msg: message.msg
|
||||||
|
};
|
||||||
|
await RocketChat.translateMessage(m, room.autoTranslateLanguage);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
|
@ -390,7 +405,6 @@ class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
actionMessage: state.messages.actionMessage,
|
|
||||||
Message_AllowDeleting: state.settings.Message_AllowDeleting,
|
Message_AllowDeleting: state.settings.Message_AllowDeleting,
|
||||||
Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes,
|
Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes,
|
||||||
Message_AllowEditing: state.settings.Message_AllowEditing,
|
Message_AllowEditing: state.settings.Message_AllowEditing,
|
||||||
|
@ -400,14 +414,4 @@ const mapStateToProps = state => ({
|
||||||
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users
|
Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
export default connect(mapStateToProps)(MessageActions);
|
||||||
actionsHide: () => dispatch(actionsHideAction()),
|
|
||||||
deleteRequest: message => dispatch(deleteRequestAction(message)),
|
|
||||||
editInit: message => dispatch(editInitAction(message)),
|
|
||||||
toggleStarRequest: message => dispatch(toggleStarRequestAction(message)),
|
|
||||||
togglePinRequest: message => dispatch(togglePinRequestAction(message)),
|
|
||||||
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message)),
|
|
||||||
replyInit: (message, mention) => dispatch(replyInitAction(message, mention))
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(MessageActions);
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import moment from 'moment';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import Markdown from '../markdown';
|
import Markdown from '../markdown';
|
||||||
import { getCustomEmoji } from '../message/utils';
|
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import {
|
import {
|
||||||
|
@ -55,7 +54,8 @@ class ReplyPreview extends Component {
|
||||||
Message_TimeFormat: PropTypes.string.isRequired,
|
Message_TimeFormat: PropTypes.string.isRequired,
|
||||||
close: PropTypes.func.isRequired,
|
close: PropTypes.func.isRequired,
|
||||||
baseUrl: PropTypes.string.isRequired,
|
baseUrl: PropTypes.string.isRequired,
|
||||||
username: PropTypes.string.isRequired
|
username: PropTypes.string.isRequired,
|
||||||
|
getCustomEmoji: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate() {
|
shouldComponentUpdate() {
|
||||||
|
@ -69,7 +69,7 @@ class ReplyPreview extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
message, Message_TimeFormat, baseUrl, username, useMarkdown
|
message, Message_TimeFormat, baseUrl, username, useMarkdown, getCustomEmoji
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const time = moment(message.ts).format(Message_TimeFormat);
|
const time = moment(message.ts).format(Message_TimeFormat);
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -10,16 +10,12 @@ import ImagePicker from 'react-native-image-crop-picker';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
import DocumentPicker from 'react-native-document-picker';
|
import DocumentPicker from 'react-native-document-picker';
|
||||||
import ActionSheet from 'react-native-action-sheet';
|
import ActionSheet from 'react-native-action-sheet';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
import { userTyping as userTypingAction } from '../../actions/room';
|
import { userTyping as userTypingAction } from '../../actions/room';
|
||||||
import {
|
|
||||||
editRequest as editRequestAction,
|
|
||||||
editCancel as editCancelAction,
|
|
||||||
replyCancel as replyCancelAction
|
|
||||||
} from '../../actions/messages';
|
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import database from '../../lib/realm';
|
import database from '../../lib/database';
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||||
import { emojis } from '../../emojis';
|
import { emojis } from '../../emojis';
|
||||||
|
@ -40,10 +36,6 @@ const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
|
||||||
const MENTIONS_TRACKING_TYPE_COMMANDS = '/';
|
const MENTIONS_TRACKING_TYPE_COMMANDS = '/';
|
||||||
const MENTIONS_COUNT_TO_DISPLAY = 4;
|
const MENTIONS_COUNT_TO_DISPLAY = 4;
|
||||||
|
|
||||||
const onlyUnique = function onlyUnique(value, index, self) {
|
|
||||||
return self.indexOf(({ _id }) => value._id === _id) === index;
|
|
||||||
};
|
|
||||||
|
|
||||||
const imagePickerConfig = {
|
const imagePickerConfig = {
|
||||||
cropping: true,
|
cropping: true,
|
||||||
compressImageQuality: 0.8,
|
compressImageQuality: 0.8,
|
||||||
|
@ -69,7 +61,6 @@ class MessageBox extends Component {
|
||||||
rid: PropTypes.string.isRequired,
|
rid: PropTypes.string.isRequired,
|
||||||
baseUrl: PropTypes.string.isRequired,
|
baseUrl: PropTypes.string.isRequired,
|
||||||
message: PropTypes.object,
|
message: PropTypes.object,
|
||||||
replyMessage: PropTypes.object,
|
|
||||||
replying: PropTypes.bool,
|
replying: PropTypes.bool,
|
||||||
editing: PropTypes.bool,
|
editing: PropTypes.bool,
|
||||||
threadsEnabled: PropTypes.bool,
|
threadsEnabled: PropTypes.bool,
|
||||||
|
@ -81,11 +72,13 @@ class MessageBox extends Component {
|
||||||
}),
|
}),
|
||||||
roomType: PropTypes.string,
|
roomType: PropTypes.string,
|
||||||
tmid: PropTypes.string,
|
tmid: PropTypes.string,
|
||||||
|
replyWithMention: PropTypes.bool,
|
||||||
|
getCustomEmoji: PropTypes.func,
|
||||||
editCancel: PropTypes.func.isRequired,
|
editCancel: PropTypes.func.isRequired,
|
||||||
editRequest: PropTypes.func.isRequired,
|
editRequest: PropTypes.func.isRequired,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
typing: PropTypes.func,
|
typing: PropTypes.func,
|
||||||
closeReply: PropTypes.func
|
replyCancel: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -102,11 +95,6 @@ class MessageBox extends Component {
|
||||||
commandPreview: []
|
commandPreview: []
|
||||||
};
|
};
|
||||||
this.showCommandPreview = false;
|
this.showCommandPreview = false;
|
||||||
this.commands = [];
|
|
||||||
this.users = [];
|
|
||||||
this.rooms = [];
|
|
||||||
this.emojis = [];
|
|
||||||
this.customEmojis = [];
|
|
||||||
this.onEmojiSelected = this.onEmojiSelected.bind(this);
|
this.onEmojiSelected = this.onEmojiSelected.bind(this);
|
||||||
this.text = '';
|
this.text = '';
|
||||||
this.fileOptions = [
|
this.fileOptions = [
|
||||||
|
@ -135,20 +123,34 @@ class MessageBox extends Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
async componentDidMount() {
|
||||||
|
const db = database.active;
|
||||||
const { rid, tmid } = this.props;
|
const { rid, tmid } = this.props;
|
||||||
let msg;
|
let msg;
|
||||||
|
try {
|
||||||
|
const threadsCollection = db.collections.get('threads');
|
||||||
|
const subsCollection = db.collections.get('subscriptions');
|
||||||
if (tmid) {
|
if (tmid) {
|
||||||
const thread = database.objectForPrimaryKey('threads', tmid);
|
try {
|
||||||
|
const thread = await threadsCollection.find(tmid);
|
||||||
if (thread) {
|
if (thread) {
|
||||||
msg = thread.draftMessage;
|
msg = thread.draftMessage;
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Messagebox.didMount: Thread not found');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
try {
|
||||||
if (room) {
|
const room = await subsCollection.find(rid);
|
||||||
msg = room.draftMessage;
|
msg = room.draftMessage;
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Messagebox.didMount: Room not found');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
|
||||||
if (msg) {
|
if (msg) {
|
||||||
this.setInput(msg);
|
this.setInput(msg);
|
||||||
this.setShowSend(true);
|
this.setShowSend(true);
|
||||||
|
@ -160,17 +162,16 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const { message, replyMessage, isFocused } = this.props;
|
const { isFocused, editing, replying } = this.props;
|
||||||
if (!isFocused) {
|
if (!isFocused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!equal(message, nextProps.message) && nextProps.message.msg) {
|
if (editing !== nextProps.editing && nextProps.editing) {
|
||||||
this.setInput(nextProps.message.msg);
|
this.setInput(nextProps.message.msg);
|
||||||
if (this.text) {
|
if (this.text) {
|
||||||
this.setShowSend(true);
|
this.setShowSend(true);
|
||||||
}
|
}
|
||||||
this.focus();
|
} else if (replying !== nextProps.replying && nextProps.replying) {
|
||||||
} else if (!equal(replyMessage, nextProps.replyMessage)) {
|
|
||||||
this.focus();
|
this.focus();
|
||||||
} else if (!nextProps.message) {
|
} else if (!nextProps.message) {
|
||||||
this.clearInput();
|
this.clearInput();
|
||||||
|
@ -181,6 +182,7 @@ class MessageBox extends Component {
|
||||||
const {
|
const {
|
||||||
showEmojiKeyboard, showSend, recording, mentions, file, commandPreview
|
showEmojiKeyboard, showSend, recording, mentions, file, commandPreview
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
roomType, replying, editing, isFocused
|
roomType, replying, editing, isFocused
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -217,7 +219,12 @@ class MessageBox extends Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeText = debounce((text) => {
|
componentWillUnmount() {
|
||||||
|
console.countReset(`${ this.constructor.name }.render calls`);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeText = debounce(async(text) => {
|
||||||
|
const db = database.active;
|
||||||
const isTextEmpty = text.length === 0;
|
const isTextEmpty = text.length === 0;
|
||||||
this.setShowSend(!isTextEmpty);
|
this.setShowSend(!isTextEmpty);
|
||||||
this.handleTyping(!isTextEmpty);
|
this.handleTyping(!isTextEmpty);
|
||||||
|
@ -226,10 +233,15 @@ class MessageBox extends Component {
|
||||||
const slashCommand = text.match(/^\/([a-z0-9._-]+) (.+)/im);
|
const slashCommand = text.match(/^\/([a-z0-9._-]+) (.+)/im);
|
||||||
if (slashCommand) {
|
if (slashCommand) {
|
||||||
const [, name, params] = slashCommand;
|
const [, name, params] = slashCommand;
|
||||||
const command = database.objects('slashCommand').filtered('command == $0', name);
|
const commandsCollection = db.collections.get('slash_commands');
|
||||||
if (command && command[0] && command[0].providesPreview) {
|
try {
|
||||||
|
const command = await commandsCollection.find(name);
|
||||||
|
if (command.providesPreview) {
|
||||||
return this.setCommandPreview(name, params);
|
return this.setCommandPreview(name, params);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Slash command not found');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isTextEmpty) {
|
if (!isTextEmpty) {
|
||||||
|
@ -324,114 +336,49 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getFixedMentions = (keyword) => {
|
getFixedMentions = (keyword) => {
|
||||||
|
let result = [];
|
||||||
if ('all'.indexOf(keyword) !== -1) {
|
if ('all'.indexOf(keyword) !== -1) {
|
||||||
this.users = [{ _id: -1, username: 'all' }, ...this.users];
|
result = [{ _id: -1, username: 'all' }];
|
||||||
}
|
}
|
||||||
if ('here'.indexOf(keyword) !== -1) {
|
if ('here'.indexOf(keyword) !== -1) {
|
||||||
this.users = [{ _id: -2, username: 'here' }, ...this.users];
|
result = [{ _id: -2, username: 'here' }, ...result];
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUsers = async(keyword) => {
|
getUsers = debounce(async(keyword) => {
|
||||||
this.users = database.objects('users');
|
let res = await RocketChat.search({ text: keyword, filterRooms: false, filterUsers: true });
|
||||||
|
res = [...this.getFixedMentions(keyword), ...res];
|
||||||
|
this.setState({ mentions: res });
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
getRooms = debounce(async(keyword = '') => {
|
||||||
|
const res = await RocketChat.search({ text: keyword, filterRooms: true, filterUsers: false });
|
||||||
|
this.setState({ mentions: res });
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
getEmojis = debounce(async(keyword) => {
|
||||||
|
const db = database.active;
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
this.users = this.users.filtered('username CONTAINS[c] $0', keyword);
|
const customEmojisCollection = db.collections.get('custom_emojis');
|
||||||
|
let customEmojis = await customEmojisCollection.query(
|
||||||
|
Q.where('name', Q.like(`${ Q.sanitizeLikeString(keyword) }%`))
|
||||||
|
).fetch();
|
||||||
|
customEmojis = customEmojis.slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||||
|
const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||||
|
const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||||
|
this.setState({ mentions: mergedEmojis || [] });
|
||||||
}
|
}
|
||||||
this.getFixedMentions(keyword);
|
}, 300)
|
||||||
this.setState({ mentions: this.users.slice() });
|
|
||||||
|
|
||||||
const usernames = [];
|
getSlashCommands = debounce(async(keyword) => {
|
||||||
|
const db = database.active;
|
||||||
if (keyword && this.users.length > 7) {
|
const commandsCollection = db.collections.get('slash_commands');
|
||||||
return;
|
const commands = await commandsCollection.query(
|
||||||
}
|
Q.where('id', Q.like(`${ Q.sanitizeLikeString(keyword) }%`))
|
||||||
|
).fetch();
|
||||||
this.users.forEach(user => usernames.push(user.username));
|
this.setState({ mentions: commands || [] });
|
||||||
|
}, 300)
|
||||||
if (this.oldPromise) {
|
|
||||||
this.oldPromise();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const results = await Promise.race([
|
|
||||||
RocketChat.spotlight(keyword, usernames, { users: true }),
|
|
||||||
new Promise((resolve, reject) => (this.oldPromise = reject))
|
|
||||||
]);
|
|
||||||
if (results.users && results.users.length) {
|
|
||||||
database.write(() => {
|
|
||||||
results.users.forEach((user) => {
|
|
||||||
try {
|
|
||||||
database.create('users', user, true);
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('spotlight canceled');
|
|
||||||
} finally {
|
|
||||||
delete this.oldPromise;
|
|
||||||
this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword).slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
|
||||||
this.getFixedMentions(keyword);
|
|
||||||
this.setState({ mentions: this.users });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getRooms = async(keyword = '') => {
|
|
||||||
this.roomsCache = this.roomsCache || [];
|
|
||||||
this.rooms = database.objects('subscriptions')
|
|
||||||
.filtered('t != $0', 'd');
|
|
||||||
if (keyword) {
|
|
||||||
this.rooms = this.rooms.filtered('name CONTAINS[c] $0', keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rooms = [];
|
|
||||||
this.rooms.forEach(room => rooms.push(room));
|
|
||||||
|
|
||||||
this.roomsCache.forEach((room) => {
|
|
||||||
if (room.name && room.name.toUpperCase().indexOf(keyword.toUpperCase()) !== -1) {
|
|
||||||
rooms.push(room);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (rooms.length > 3) {
|
|
||||||
this.setState({ mentions: rooms });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.oldPromise) {
|
|
||||||
this.oldPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const results = await Promise.race([
|
|
||||||
RocketChat.spotlight(keyword, [...rooms, ...this.roomsCache].map(r => r.name), { rooms: true }),
|
|
||||||
new Promise((resolve, reject) => (this.oldPromise = reject))
|
|
||||||
]);
|
|
||||||
if (results.rooms && results.rooms.length) {
|
|
||||||
this.roomsCache = [...this.roomsCache, ...results.rooms].filter(onlyUnique);
|
|
||||||
}
|
|
||||||
this.setState({ mentions: [...rooms.slice(), ...results.rooms] });
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('spotlight canceled');
|
|
||||||
} finally {
|
|
||||||
delete this.oldPromise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getEmojis = (keyword) => {
|
|
||||||
if (keyword) {
|
|
||||||
this.customEmojis = database.objects('customEmojis').filtered('name CONTAINS[c] $0', keyword).slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
|
||||||
this.emojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
|
||||||
const mergedEmojis = [...this.customEmojis, ...this.emojis].slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
|
||||||
this.setState({ mentions: mergedEmojis });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getSlashCommands = (keyword) => {
|
|
||||||
this.commands = database.objects('slashCommand').filtered('command CONTAINS[c] $0', keyword);
|
|
||||||
this.setState({ mentions: this.commands });
|
|
||||||
}
|
|
||||||
|
|
||||||
focus = () => {
|
focus = () => {
|
||||||
if (this.component && this.component.focus) {
|
if (this.component && this.component.focus) {
|
||||||
|
@ -629,7 +576,7 @@ class MessageBox extends Component {
|
||||||
|
|
||||||
submit = async() => {
|
submit = async() => {
|
||||||
const {
|
const {
|
||||||
message: editingMessage, editRequest, onSubmit, rid: roomId
|
onSubmit, rid: roomId
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const message = this.text;
|
const message = this.text;
|
||||||
|
|
||||||
|
@ -646,10 +593,13 @@ class MessageBox extends Component {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// Slash command
|
// Slash command
|
||||||
|
|
||||||
if (message[0] === MENTIONS_TRACKING_TYPE_COMMANDS) {
|
if (message[0] === MENTIONS_TRACKING_TYPE_COMMANDS) {
|
||||||
|
const db = database.active;
|
||||||
|
const commandsCollection = db.collections.get('slash_commands');
|
||||||
const command = message.replace(/ .*/, '').slice(1);
|
const command = message.replace(/ .*/, '').slice(1);
|
||||||
const slashCommand = database.objects('slashCommand').filtered('command CONTAINS[c] $0', command);
|
const slashCommand = await commandsCollection.query(
|
||||||
|
Q.where('id', Q.like(`${ Q.sanitizeLikeString(command) }%`))
|
||||||
|
).fetch();
|
||||||
if (slashCommand.length > 0) {
|
if (slashCommand.length > 0) {
|
||||||
try {
|
try {
|
||||||
const messageWithoutCommand = message.substr(message.indexOf(' ') + 1);
|
const messageWithoutCommand = message.substr(message.indexOf(' ') + 1);
|
||||||
|
@ -663,32 +613,35 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
// Edit
|
// Edit
|
||||||
if (editing) {
|
if (editing) {
|
||||||
const { _id, rid } = editingMessage;
|
const { message: editingMessage, editRequest } = this.props;
|
||||||
editRequest({ _id, msg: message, rid });
|
const { id, subscription: { id: rid } } = editingMessage;
|
||||||
|
editRequest({ id, msg: message, rid });
|
||||||
|
|
||||||
// Reply
|
// Reply
|
||||||
} else if (replying) {
|
} else if (replying) {
|
||||||
const { replyMessage, closeReply, threadsEnabled } = this.props;
|
const {
|
||||||
|
message: replyingMessage, replyCancel, threadsEnabled, replyWithMention
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
// Thread
|
// Thread
|
||||||
if (threadsEnabled && replyMessage.mention) {
|
if (threadsEnabled && replyWithMention) {
|
||||||
onSubmit(message, replyMessage._id);
|
onSubmit(message, replyingMessage.id);
|
||||||
|
|
||||||
// Legacy reply or quote (quote is a reply without mention)
|
// Legacy reply or quote (quote is a reply without mention)
|
||||||
} else {
|
} else {
|
||||||
const { user, roomType } = this.props;
|
const { user, roomType } = this.props;
|
||||||
const permalink = await this.getPermalink(replyMessage);
|
const permalink = await this.getPermalink(replyingMessage);
|
||||||
let msg = `[ ](${ permalink }) `;
|
let msg = `[ ](${ permalink }) `;
|
||||||
|
|
||||||
// if original message wasn't sent by current user and neither from a direct room
|
// if original message wasn't sent by current user and neither from a direct room
|
||||||
if (user.username !== replyMessage.u.username && roomType !== 'd' && replyMessage.mention) {
|
if (user.username !== replyingMessage.u.username && roomType !== 'd' && replyWithMention) {
|
||||||
msg += `@${ replyMessage.u.username } `;
|
msg += `@${ replyingMessage.u.username } `;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = `${ msg } ${ message }`;
|
msg = `${ msg } ${ message }`;
|
||||||
onSubmit(msg);
|
onSubmit(msg);
|
||||||
}
|
}
|
||||||
closeReply();
|
replyCancel();
|
||||||
|
|
||||||
// Normal message
|
// Normal message
|
||||||
} else {
|
} else {
|
||||||
|
@ -726,11 +679,6 @@ class MessageBox extends Component {
|
||||||
trackingType: '',
|
trackingType: '',
|
||||||
commandPreview: []
|
commandPreview: []
|
||||||
});
|
});
|
||||||
this.users = [];
|
|
||||||
this.rooms = [];
|
|
||||||
this.customEmojis = [];
|
|
||||||
this.emojis = [];
|
|
||||||
this.commands = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFixedMentionItem = item => (
|
renderFixedMentionItem = item => (
|
||||||
|
@ -797,33 +745,33 @@ class MessageBox extends Component {
|
||||||
switch (trackingType) {
|
switch (trackingType) {
|
||||||
case MENTIONS_TRACKING_TYPE_EMOJIS:
|
case MENTIONS_TRACKING_TYPE_EMOJIS:
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
{this.renderMentionEmoji(item)}
|
{this.renderMentionEmoji(item)}
|
||||||
<Text key='mention-item-name' style={styles.mentionText}>:{ item.name || item }:</Text>
|
<Text key='mention-item-name' style={styles.mentionText}>:{ item.name || item }:</Text>
|
||||||
</React.Fragment>
|
</>
|
||||||
);
|
);
|
||||||
case MENTIONS_TRACKING_TYPE_COMMANDS:
|
case MENTIONS_TRACKING_TYPE_COMMANDS:
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
<Text key='mention-item-command' style={styles.slash}>/</Text>
|
<Text key='mention-item-command' style={styles.slash}>/</Text>
|
||||||
<Text key='mention-item-param'>{ item.command}</Text>
|
<Text key='mention-item-param'>{ item.command}</Text>
|
||||||
</React.Fragment>
|
</>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
<Avatar
|
<Avatar
|
||||||
key='mention-item-avatar'
|
key='mention-item-avatar'
|
||||||
style={styles.avatar}
|
style={styles.avatar}
|
||||||
text={item.username || item.name}
|
text={item.username || item.name}
|
||||||
size={30}
|
size={30}
|
||||||
type={item.username ? 'd' : 'c'}
|
type={item.t}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
userId={user.id}
|
userId={user.id}
|
||||||
token={user.token}
|
token={user.token}
|
||||||
/>
|
/>
|
||||||
<Text key='mention-item-name' style={styles.mentionText}>{ item.username || item.name || item }</Text>
|
<Text key='mention-item-name' style={styles.mentionText}>{ item.username || item.name || item }</Text>
|
||||||
</React.Fragment>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
@ -880,12 +828,12 @@ class MessageBox extends Component {
|
||||||
|
|
||||||
renderReplyPreview = () => {
|
renderReplyPreview = () => {
|
||||||
const {
|
const {
|
||||||
replyMessage, replying, closeReply, user
|
message, replying, replyCancel, user, getCustomEmoji
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (!replying) {
|
if (!replying) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return <ReplyPreview key='reply-preview' message={replyMessage} close={closeReply} username={user.username} />;
|
return <ReplyPreview key='reply-preview' message={message} close={replyCancel} username={user.username} getCustomEmoji={getCustomEmoji} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderContent = () => {
|
renderContent = () => {
|
||||||
|
@ -896,7 +844,7 @@ class MessageBox extends Component {
|
||||||
return (<Recording onFinish={this.finishAudioMessage} />);
|
return (<Recording onFinish={this.finishAudioMessage} />);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
{this.renderCommandPreview()}
|
{this.renderCommandPreview()}
|
||||||
{this.renderMentions()}
|
{this.renderMentions()}
|
||||||
<View style={styles.composer} key='messagebox'>
|
<View style={styles.composer} key='messagebox'>
|
||||||
|
@ -935,14 +883,15 @@ class MessageBox extends Component {
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</React.Fragment>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
console.count(`${ this.constructor.name }.render calls`);
|
||||||
const { showEmojiKeyboard, file } = this.state;
|
const { showEmojiKeyboard, file } = this.state;
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
<KeyboardAccessoryView
|
<KeyboardAccessoryView
|
||||||
renderContent={this.renderContent}
|
renderContent={this.renderContent}
|
||||||
kbInputRef={this.component}
|
kbInputRef={this.component}
|
||||||
|
@ -960,16 +909,12 @@ class MessageBox extends Component {
|
||||||
close={() => this.setState({ file: {} })}
|
close={() => this.setState({ file: {} })}
|
||||||
submit={this.sendMediaMessage}
|
submit={this.sendMediaMessage}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
message: state.messages.message,
|
|
||||||
replyMessage: state.messages.replyMessage,
|
|
||||||
replying: state.messages.replying,
|
|
||||||
editing: state.messages.editing,
|
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
|
||||||
threadsEnabled: state.settings.Threads_enabled,
|
threadsEnabled: state.settings.Threads_enabled,
|
||||||
user: {
|
user: {
|
||||||
|
@ -980,10 +925,7 @@ const mapStateToProps = state => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const dispatchToProps = ({
|
const dispatchToProps = ({
|
||||||
editCancel: () => editCancelAction(),
|
typing: (rid, status) => userTypingAction(rid, status)
|
||||||
editRequest: message => editRequestAction(message),
|
|
||||||
typing: (rid, status) => userTypingAction(rid, status),
|
|
||||||
closeReply: () => replyCancelAction()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(MessageBox);
|
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(MessageBox);
|
||||||
|
|
|
@ -1,33 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import ActionSheet from 'react-native-action-sheet';
|
import ActionSheet from 'react-native-action-sheet';
|
||||||
|
|
||||||
import { errorActionsHide as errorActionsHideAction } from '../actions/messages';
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/database';
|
||||||
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
|
||||||
class MessageErrorActions extends React.Component {
|
class MessageErrorActions extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
errorActionsHide: PropTypes.func.isRequired,
|
actionsHide: PropTypes.func.isRequired,
|
||||||
actionMessage: PropTypes.object
|
message: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
handleResend = protectedFunction(() => {
|
|
||||||
const { actionMessage } = this.props;
|
|
||||||
RocketChat.resendMessage(actionMessage._id);
|
|
||||||
});
|
|
||||||
|
|
||||||
handleDelete = protectedFunction(() => {
|
|
||||||
const { actionMessage } = this.props;
|
|
||||||
database.write(() => {
|
|
||||||
const msg = database.objects('messages').filtered('_id = $0', actionMessage._id);
|
|
||||||
database.delete(msg);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -41,6 +26,19 @@ class MessageErrorActions extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleResend = protectedFunction(async() => {
|
||||||
|
const { message } = this.props;
|
||||||
|
await RocketChat.resendMessage(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
handleDelete = protectedFunction(async() => {
|
||||||
|
const { message } = this.props;
|
||||||
|
const db = database.active;
|
||||||
|
await db.action(async() => {
|
||||||
|
await message.destroyPermanently();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
showActionSheet = () => {
|
showActionSheet = () => {
|
||||||
ActionSheet.showActionSheetWithOptions({
|
ActionSheet.showActionSheetWithOptions({
|
||||||
options: this.options,
|
options: this.options,
|
||||||
|
@ -53,7 +51,7 @@ class MessageErrorActions extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleActionPress = (actionIndex) => {
|
handleActionPress = (actionIndex) => {
|
||||||
const { errorActionsHide } = this.props;
|
const { actionsHide } = this.props;
|
||||||
switch (actionIndex) {
|
switch (actionIndex) {
|
||||||
case this.RESEND_INDEX:
|
case this.RESEND_INDEX:
|
||||||
this.handleResend();
|
this.handleResend();
|
||||||
|
@ -64,7 +62,7 @@ class MessageErrorActions extends React.Component {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
errorActionsHide();
|
actionsHide();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -74,12 +72,4 @@ class MessageErrorActions extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
export default MessageErrorActions;
|
||||||
actionMessage: state.messages.actionMessage
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
errorActionsHide: () => dispatch(errorActionsHideAction())
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(MessageErrorActions);
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import Modal from 'react-native-modal';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
import Emoji from './message/Emoji';
|
import Emoji from './message/Emoji';
|
||||||
import { getCustomEmoji } from './message/utils';
|
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from '../lib/Icons';
|
||||||
import sharedStyles from '../views/Styles';
|
import sharedStyles from '../views/Styles';
|
||||||
|
@ -62,7 +61,9 @@ const styles = StyleSheet.create({
|
||||||
const standardEmojiStyle = { fontSize: 20 };
|
const standardEmojiStyle = { fontSize: 20 };
|
||||||
const customEmojiStyle = { width: 20, height: 20 };
|
const customEmojiStyle = { width: 20, height: 20 };
|
||||||
|
|
||||||
const Item = React.memo(({ item, user, baseUrl }) => {
|
const Item = React.memo(({
|
||||||
|
item, user, baseUrl, getCustomEmoji
|
||||||
|
}) => {
|
||||||
const count = item.usernames.length;
|
const count = item.usernames.length;
|
||||||
let usernames = item.usernames.slice(0, 3)
|
let usernames = item.usernames.slice(0, 3)
|
||||||
.map(username => (username === user.username ? I18n.t('you') : username)).join(', ');
|
.map(username => (username === user.username ? I18n.t('you') : username)).join(', ');
|
||||||
|
@ -146,7 +147,8 @@ ModalContent.displayName = 'ReactionsModalContent';
|
||||||
Item.propTypes = {
|
Item.propTypes = {
|
||||||
item: PropTypes.object,
|
item: PropTypes.object,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string
|
baseUrl: PropTypes.string,
|
||||||
|
getCustomEmoji: PropTypes.func
|
||||||
};
|
};
|
||||||
Item.displayName = 'ReactionsModalItem';
|
Item.displayName = 'ReactionsModalItem';
|
||||||
|
|
||||||
|
|
|
@ -3,56 +3,26 @@ import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import Status from './Status';
|
import Status from './Status';
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
|
||||||
|
|
||||||
class StatusContainer extends React.PureComponent {
|
class StatusContainer extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
id: PropTypes.string,
|
|
||||||
style: PropTypes.any,
|
style: PropTypes.any,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
offline: PropTypes.bool
|
status: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
size: 16
|
size: 16
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.user = database.memoryDatabase.objects('activeUsers').filtered('id == $0', props.id);
|
|
||||||
this.state = {
|
|
||||||
user: this.user[0] || {}
|
|
||||||
};
|
|
||||||
safeAddListener(this.user, this.updateState);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.user.removeAllListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
get status() {
|
|
||||||
const { user } = this.state;
|
|
||||||
const { offline } = this.props;
|
|
||||||
if (offline || !user) {
|
|
||||||
return 'offline';
|
|
||||||
}
|
|
||||||
return user.status || 'offline';
|
|
||||||
}
|
|
||||||
|
|
||||||
updateState = () => {
|
|
||||||
if (this.user.length) {
|
|
||||||
this.setState({ user: this.user[0] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { style, size } = this.props;
|
const { style, size, status } = this.props;
|
||||||
return <Status size={size} style={style} status={this.status} />;
|
return <Status size={size} style={style} status={status} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
offline: !state.meteor.connected
|
status: state.meteor.connected ? state.activeUsers[ownProps.id] : 'offline'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(StatusContainer);
|
export default connect(mapStateToProps)(StatusContainer);
|
||||||
|
|
|
@ -24,10 +24,10 @@ const Content = React.memo((props) => {
|
||||||
getCustomEmoji={props.getCustomEmoji}
|
getCustomEmoji={props.getCustomEmoji}
|
||||||
username={props.user.username}
|
username={props.user.username}
|
||||||
isEdited={props.isEdited}
|
isEdited={props.isEdited}
|
||||||
numberOfLines={props.tmid ? 1 : 0}
|
numberOfLines={(props.tmid && !props.isThreadRoom) ? 1 : 0}
|
||||||
channels={props.channels}
|
channels={props.channels}
|
||||||
mentions={props.mentions}
|
mentions={props.mentions}
|
||||||
useMarkdown={props.useMarkdown && !props.tmid}
|
useMarkdown={props.useMarkdown && (!props.tmid || props.isThreadRoom)}
|
||||||
navToRoomInfo={props.navToRoomInfo}
|
navToRoomInfo={props.navToRoomInfo}
|
||||||
tmid={props.tmid}
|
tmid={props.tmid}
|
||||||
/>
|
/>
|
||||||
|
@ -45,6 +45,7 @@ Content.propTypes = {
|
||||||
isTemp: PropTypes.bool,
|
isTemp: PropTypes.bool,
|
||||||
isInfo: PropTypes.bool,
|
isInfo: PropTypes.bool,
|
||||||
tmid: PropTypes.string,
|
tmid: PropTypes.string,
|
||||||
|
isThreadRoom: PropTypes.bool,
|
||||||
msg: PropTypes.string,
|
msg: PropTypes.string,
|
||||||
isEdited: PropTypes.bool,
|
isEdited: PropTypes.bool,
|
||||||
useMarkdown: PropTypes.bool,
|
useMarkdown: PropTypes.bool,
|
||||||
|
|
|
@ -32,7 +32,7 @@ const MessageAvatar = React.memo(({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, (prevProps, nextProps) => prevProps.isHeader === nextProps.isHeader);
|
});
|
||||||
|
|
||||||
MessageAvatar.propTypes = {
|
MessageAvatar.propTypes = {
|
||||||
isHeader: PropTypes.bool,
|
isHeader: PropTypes.bool,
|
||||||
|
|
|
@ -8,9 +8,9 @@ import styles from './styles';
|
||||||
import Emoji from './Emoji';
|
import Emoji from './Emoji';
|
||||||
import { BUTTON_HIT_SLOP } from './utils';
|
import { BUTTON_HIT_SLOP } from './utils';
|
||||||
|
|
||||||
const AddReaction = React.memo(({ toggleReactionPicker }) => (
|
const AddReaction = React.memo(({ reactionInit }) => (
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={toggleReactionPicker}
|
onPress={reactionInit}
|
||||||
key='message-add-reaction'
|
key='message-add-reaction'
|
||||||
testID='message-add-reaction'
|
testID='message-add-reaction'
|
||||||
style={styles.reactionButton}
|
style={styles.reactionButton}
|
||||||
|
@ -52,7 +52,7 @@ const Reaction = React.memo(({
|
||||||
});
|
});
|
||||||
|
|
||||||
const Reactions = React.memo(({
|
const Reactions = React.memo(({
|
||||||
reactions, user, baseUrl, onReactionPress, toggleReactionPicker, onReactionLongPress, getCustomEmoji
|
reactions, user, baseUrl, onReactionPress, reactionInit, onReactionLongPress, getCustomEmoji
|
||||||
}) => {
|
}) => {
|
||||||
if (!reactions || reactions.length === 0) {
|
if (!reactions || reactions.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -70,11 +70,10 @@ const Reactions = React.memo(({
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<AddReaction toggleReactionPicker={toggleReactionPicker} />
|
<AddReaction reactionInit={reactionInit} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// FIXME: can't compare because it's a Realm object (it may be fixed by JSON.parse(JSON.stringify(reactions)))
|
|
||||||
|
|
||||||
Reaction.propTypes = {
|
Reaction.propTypes = {
|
||||||
reaction: PropTypes.object,
|
reaction: PropTypes.object,
|
||||||
|
@ -91,14 +90,14 @@ Reactions.propTypes = {
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
onReactionPress: PropTypes.func,
|
onReactionPress: PropTypes.func,
|
||||||
toggleReactionPicker: PropTypes.func,
|
reactionInit: PropTypes.func,
|
||||||
onReactionLongPress: PropTypes.func,
|
onReactionLongPress: PropTypes.func,
|
||||||
getCustomEmoji: PropTypes.func
|
getCustomEmoji: PropTypes.func
|
||||||
};
|
};
|
||||||
Reactions.displayName = 'MessageReactions';
|
Reactions.displayName = 'MessageReactions';
|
||||||
|
|
||||||
AddReaction.propTypes = {
|
AddReaction.propTypes = {
|
||||||
toggleReactionPicker: PropTypes.func
|
reactionInit: PropTypes.func
|
||||||
};
|
};
|
||||||
AddReaction.displayName = 'MessageAddReaction';
|
AddReaction.displayName = 'MessageAddReaction';
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,14 @@ import DisclosureIndicator from '../DisclosureIndicator';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
const RepliedThread = React.memo(({
|
const RepliedThread = React.memo(({
|
||||||
tmid, tmsg, isHeader, isTemp, fetchThreadName
|
tmid, tmsg, isHeader, isTemp, fetchThreadName, id
|
||||||
}) => {
|
}) => {
|
||||||
if (!tmid || !isHeader || isTemp) {
|
if (!tmid || !isHeader || isTemp) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tmsg) {
|
if (!tmsg) {
|
||||||
fetchThreadName(tmid);
|
fetchThreadName(tmid, id);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ const RepliedThread = React.memo(({
|
||||||
RepliedThread.propTypes = {
|
RepliedThread.propTypes = {
|
||||||
tmid: PropTypes.string,
|
tmid: PropTypes.string,
|
||||||
tmsg: PropTypes.string,
|
tmsg: PropTypes.string,
|
||||||
|
id: PropTypes.string,
|
||||||
isHeader: PropTypes.bool,
|
isHeader: PropTypes.bool,
|
||||||
isTemp: PropTypes.bool,
|
isTemp: PropTypes.bool,
|
||||||
fetchThreadName: PropTypes.func
|
fetchThreadName: PropTypes.func
|
||||||
|
|
|
@ -8,9 +8,9 @@ import { CustomIcon } from '../../lib/Icons';
|
||||||
import { THREAD } from './constants';
|
import { THREAD } from './constants';
|
||||||
|
|
||||||
const Thread = React.memo(({
|
const Thread = React.memo(({
|
||||||
msg, tcount, tlm, customThreadTimeFormat
|
msg, tcount, tlm, customThreadTimeFormat, isThreadRoom
|
||||||
}) => {
|
}) => {
|
||||||
if (!tlm) {
|
if (!tlm || isThreadRoom) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,8 @@ Thread.propTypes = {
|
||||||
msg: PropTypes.string,
|
msg: PropTypes.string,
|
||||||
tcount: PropTypes.string,
|
tcount: PropTypes.string,
|
||||||
tlm: PropTypes.string,
|
tlm: PropTypes.string,
|
||||||
customThreadTimeFormat: PropTypes.string
|
customThreadTimeFormat: PropTypes.string,
|
||||||
|
isThreadRoom: PropTypes.bool
|
||||||
};
|
};
|
||||||
Thread.displayName = 'MessageThread';
|
Thread.displayName = 'MessageThread';
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { KeyboardUtils } from 'react-native-keyboard-input';
|
||||||
|
|
||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import { SYSTEM_MESSAGES, getCustomEmoji, getMessageTranslation } from './utils';
|
import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
|
||||||
import messagesStatus from '../../constants/messagesStatus';
|
import messagesStatus from '../../constants/messagesStatus';
|
||||||
|
|
||||||
export default class MessageContainer extends React.Component {
|
export default class MessageContainer extends React.Component {
|
||||||
|
@ -21,22 +21,23 @@ export default class MessageContainer extends React.Component {
|
||||||
archived: PropTypes.bool,
|
archived: PropTypes.bool,
|
||||||
broadcast: PropTypes.bool,
|
broadcast: PropTypes.bool,
|
||||||
previousItem: PropTypes.object,
|
previousItem: PropTypes.object,
|
||||||
_updatedAt: PropTypes.instanceOf(Date),
|
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
Message_GroupingPeriod: PropTypes.number,
|
Message_GroupingPeriod: PropTypes.number,
|
||||||
isReadReceiptEnabled: PropTypes.bool,
|
isReadReceiptEnabled: PropTypes.bool,
|
||||||
|
isThreadRoom: PropTypes.bool,
|
||||||
useRealName: PropTypes.bool,
|
useRealName: PropTypes.bool,
|
||||||
useMarkdown: PropTypes.bool,
|
useMarkdown: PropTypes.bool,
|
||||||
autoTranslateRoom: PropTypes.bool,
|
autoTranslateRoom: PropTypes.bool,
|
||||||
autoTranslateLanguage: PropTypes.string,
|
autoTranslateLanguage: PropTypes.string,
|
||||||
status: PropTypes.number,
|
status: PropTypes.number,
|
||||||
|
getCustomEmoji: PropTypes.func,
|
||||||
onLongPress: PropTypes.func,
|
onLongPress: PropTypes.func,
|
||||||
onReactionPress: PropTypes.func,
|
onReactionPress: PropTypes.func,
|
||||||
onDiscussionPress: PropTypes.func,
|
onDiscussionPress: PropTypes.func,
|
||||||
onThreadPress: PropTypes.func,
|
onThreadPress: PropTypes.func,
|
||||||
errorActionsShow: PropTypes.func,
|
errorActionsShow: PropTypes.func,
|
||||||
replyBroadcast: PropTypes.func,
|
replyBroadcast: PropTypes.func,
|
||||||
toggleReactionPicker: PropTypes.func,
|
reactionInit: PropTypes.func,
|
||||||
fetchThreadName: PropTypes.func,
|
fetchThreadName: PropTypes.func,
|
||||||
onOpenFileModal: PropTypes.func,
|
onOpenFileModal: PropTypes.func,
|
||||||
onReactionLongPress: PropTypes.func,
|
onReactionLongPress: PropTypes.func,
|
||||||
|
@ -45,55 +46,53 @@ export default class MessageContainer extends React.Component {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onLongPress: () => {},
|
onLongPress: () => {},
|
||||||
_updatedAt: new Date(),
|
|
||||||
archived: false,
|
archived: false,
|
||||||
broadcast: false
|
broadcast: false
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
componentDidMount() {
|
||||||
const {
|
const { item } = this.props;
|
||||||
status, item, _updatedAt, autoTranslateRoom
|
if (item && item.observe) {
|
||||||
} = this.props;
|
const observable = item.observe();
|
||||||
|
this.subscription = observable.subscribe(() => {
|
||||||
if (status !== nextProps.status) {
|
this.forceUpdate();
|
||||||
return true;
|
});
|
||||||
}
|
}
|
||||||
if (autoTranslateRoom !== nextProps.autoTranslateRoom) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (item.tmsg !== nextProps.item.tmsg) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (item.unread !== nextProps.item.unread) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return _updatedAt.toISOString() !== nextProps._updatedAt.toISOString();
|
shouldComponentUpdate() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.subscription && this.subscription.unsubscribe) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onPress = debounce(() => {
|
onPress = debounce(() => {
|
||||||
const { item } = this.props;
|
const { item, isThreadRoom } = this.props;
|
||||||
KeyboardUtils.dismiss();
|
KeyboardUtils.dismiss();
|
||||||
|
|
||||||
if ((item.tlm || item.tmid)) {
|
if (((item.tlm || item.tmid) && !isThreadRoom)) {
|
||||||
this.onThreadPress();
|
this.onThreadPress();
|
||||||
}
|
}
|
||||||
}, 300, true);
|
}, 300, true);
|
||||||
|
|
||||||
onLongPress = () => {
|
onLongPress = () => {
|
||||||
const { archived, onLongPress } = this.props;
|
const { archived, onLongPress, item } = this.props;
|
||||||
if (this.isInfo || this.hasError || archived) {
|
if (this.isInfo || this.hasError || archived) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (onLongPress) {
|
if (onLongPress) {
|
||||||
onLongPress(this.parseMessage());
|
onLongPress(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onErrorPress = () => {
|
onErrorPress = () => {
|
||||||
const { errorActionsShow } = this.props;
|
const { errorActionsShow, item } = this.props;
|
||||||
if (errorActionsShow) {
|
if (errorActionsShow) {
|
||||||
errorActionsShow(this.parseMessage());
|
errorActionsShow(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +131,7 @@ export default class MessageContainer extends React.Component {
|
||||||
if (this.hasError || (previousItem && previousItem.status === messagesStatus.ERROR)) {
|
if (this.hasError || (previousItem && previousItem.status === messagesStatus.ERROR)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
if (previousItem && (
|
if (previousItem && (
|
||||||
(previousItem.ts.toDateString() === item.ts.toDateString())
|
(previousItem.ts.toDateString() === item.ts.toDateString())
|
||||||
&& (previousItem.u.username === item.u.username)
|
&& (previousItem.u.username === item.u.username)
|
||||||
|
@ -142,13 +142,19 @@ export default class MessageContainer extends React.Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isThreadReply() {
|
get isThreadReply() {
|
||||||
const {
|
const {
|
||||||
item, previousItem
|
item, previousItem, isThreadRoom
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (previousItem && item.tmid && (previousItem.tmid !== item.tmid) && (previousItem._id !== item.tmid)) {
|
if (isThreadRoom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (previousItem && item.tmid && (previousItem.tmid !== item.tmid) && (previousItem.id !== item.tmid)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -156,9 +162,12 @@ export default class MessageContainer extends React.Component {
|
||||||
|
|
||||||
get isThreadSequential() {
|
get isThreadSequential() {
|
||||||
const {
|
const {
|
||||||
item, previousItem
|
item, previousItem, isThreadRoom
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (previousItem && item.tmid && ((previousItem.tmid === item.tmid) || (previousItem._id === item.tmid))) {
|
if (isThreadRoom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (previousItem && item.tmid && ((previousItem.tmid === item.tmid) || (previousItem.id === item.tmid))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -179,31 +188,26 @@ export default class MessageContainer extends React.Component {
|
||||||
return item.status === messagesStatus.ERROR;
|
return item.status === messagesStatus.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMessage = () => {
|
reactionInit = () => {
|
||||||
const { item } = this.props;
|
const { reactionInit, item } = this.props;
|
||||||
return JSON.parse(JSON.stringify(item));
|
if (reactionInit) {
|
||||||
}
|
reactionInit(item);
|
||||||
|
|
||||||
toggleReactionPicker = () => {
|
|
||||||
const { toggleReactionPicker } = this.props;
|
|
||||||
if (toggleReactionPicker) {
|
|
||||||
toggleReactionPicker(this.parseMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replyBroadcast = () => {
|
replyBroadcast = () => {
|
||||||
const { replyBroadcast } = this.props;
|
const { replyBroadcast, item } = this.props;
|
||||||
if (replyBroadcast) {
|
if (replyBroadcast) {
|
||||||
replyBroadcast(this.parseMessage());
|
replyBroadcast(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo
|
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
_id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, autoTranslate: autoTranslateMessage
|
id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, autoTranslate: autoTranslateMessage
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
let message = msg;
|
let message = msg;
|
||||||
|
@ -215,7 +219,7 @@ export default class MessageContainer extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Message
|
<Message
|
||||||
id={_id}
|
id={id}
|
||||||
msg={message}
|
msg={message}
|
||||||
author={u}
|
author={u}
|
||||||
ts={ts}
|
ts={ts}
|
||||||
|
@ -251,6 +255,7 @@ export default class MessageContainer extends React.Component {
|
||||||
isHeader={this.isHeader}
|
isHeader={this.isHeader}
|
||||||
isThreadReply={this.isThreadReply}
|
isThreadReply={this.isThreadReply}
|
||||||
isThreadSequential={this.isThreadSequential}
|
isThreadSequential={this.isThreadSequential}
|
||||||
|
isThreadRoom={isThreadRoom}
|
||||||
isInfo={this.isInfo}
|
isInfo={this.isInfo}
|
||||||
isTemp={this.isTemp}
|
isTemp={this.isTemp}
|
||||||
hasError={this.hasError}
|
hasError={this.hasError}
|
||||||
|
@ -260,7 +265,7 @@ export default class MessageContainer extends React.Component {
|
||||||
onReactionLongPress={this.onReactionLongPress}
|
onReactionLongPress={this.onReactionLongPress}
|
||||||
onReactionPress={this.onReactionPress}
|
onReactionPress={this.onReactionPress}
|
||||||
replyBroadcast={this.replyBroadcast}
|
replyBroadcast={this.replyBroadcast}
|
||||||
toggleReactionPicker={this.toggleReactionPicker}
|
reactionInit={this.reactionInit}
|
||||||
onDiscussionPress={this.onDiscussionPress}
|
onDiscussionPress={this.onDiscussionPress}
|
||||||
onOpenFileModal={onOpenFileModal}
|
onOpenFileModal={onOpenFileModal}
|
||||||
getCustomEmoji={getCustomEmoji}
|
getCustomEmoji={getCustomEmoji}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import database from '../../lib/realm';
|
|
||||||
import { DISCUSSION } from './constants';
|
import { DISCUSSION } from './constants';
|
||||||
|
|
||||||
export const formatLastMessage = (lm, customFormat) => {
|
export const formatLastMessage = (lm, customFormat) => {
|
||||||
|
@ -96,25 +95,6 @@ export const getInfoMessage = ({
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCustomEmoji = (content) => {
|
|
||||||
// search by name
|
|
||||||
const data = database.objects('customEmojis').filtered('name == $0', content);
|
|
||||||
if (data.length) {
|
|
||||||
return data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// searches by alias
|
|
||||||
// RealmJS doesn't support IN operator: https://github.com/realm/realm-js/issues/450
|
|
||||||
const emojis = database.objects('customEmojis');
|
|
||||||
const findByAlias = emojis.find((emoji) => {
|
|
||||||
if (emoji.aliases.length && emoji.aliases.findIndex(alias => alias === content) !== -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
return findByAlias;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMessageTranslation = (message, autoTranslateLanguage) => {
|
export const getMessageTranslation = (message, autoTranslateLanguage) => {
|
||||||
if (!autoTranslateLanguage) {
|
if (!autoTranslateLanguage) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { Database } from '@nozbe/watermelondb';
|
||||||
|
import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite';
|
||||||
|
import RNFetchBlob from 'rn-fetch-blob';
|
||||||
|
|
||||||
|
import Subscription from './model/Subscription';
|
||||||
|
import Room from './model/Room';
|
||||||
|
import Message from './model/Message';
|
||||||
|
import Thread from './model/Thread';
|
||||||
|
import ThreadMessage from './model/ThreadMessage';
|
||||||
|
import CustomEmoji from './model/CustomEmoji';
|
||||||
|
import FrequentlyUsedEmoji from './model/FrequentlyUsedEmoji';
|
||||||
|
import Upload from './model/Upload';
|
||||||
|
import Setting from './model/Setting';
|
||||||
|
import Role from './model/Role';
|
||||||
|
import Permission from './model/Permission';
|
||||||
|
import SlashCommand from './model/SlashCommand';
|
||||||
|
import User from './model/User';
|
||||||
|
import Server from './model/Server';
|
||||||
|
|
||||||
|
import serversSchema from './schema/servers';
|
||||||
|
import appSchema from './schema/app';
|
||||||
|
|
||||||
|
import { isIOS } from '../../utils/deviceInfo';
|
||||||
|
|
||||||
|
const appGroupPath = isIOS ? `${ RNFetchBlob.fs.syncPathAppGroup('group.ios.chat.rocket') }/` : '';
|
||||||
|
|
||||||
|
if (__DEV__ && isIOS) {
|
||||||
|
console.log(appGroupPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DB {
|
||||||
|
databases = {
|
||||||
|
serversDB: new Database({
|
||||||
|
adapter: new SQLiteAdapter({
|
||||||
|
dbName: `${ appGroupPath }default.db`,
|
||||||
|
schema: serversSchema
|
||||||
|
}),
|
||||||
|
modelClasses: [Server, User],
|
||||||
|
actionsEnabled: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get active() {
|
||||||
|
return this.databases.activeDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
get servers() {
|
||||||
|
return this.databases.serversDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveDB(database = '') {
|
||||||
|
const path = database.replace(/(^\w+:|^)\/\//, '');
|
||||||
|
const dbName = `${ appGroupPath }${ path }.db`;
|
||||||
|
|
||||||
|
const adapter = new SQLiteAdapter({
|
||||||
|
dbName,
|
||||||
|
schema: appSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
this.databases.activeDB = new Database({
|
||||||
|
adapter,
|
||||||
|
modelClasses: [
|
||||||
|
Subscription,
|
||||||
|
Room,
|
||||||
|
Message,
|
||||||
|
Thread,
|
||||||
|
ThreadMessage,
|
||||||
|
CustomEmoji,
|
||||||
|
FrequentlyUsedEmoji,
|
||||||
|
Upload,
|
||||||
|
Setting,
|
||||||
|
Role,
|
||||||
|
Permission,
|
||||||
|
SlashCommand
|
||||||
|
],
|
||||||
|
actionsEnabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = new DB();
|
||||||
|
export default db;
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import { field, date, json } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export default class CustomEmoji extends Model {
|
||||||
|
static table = 'custom_emojis';
|
||||||
|
|
||||||
|
@field('name') name;
|
||||||
|
|
||||||
|
@json('aliases', sanitizer) aliases;
|
||||||
|
|
||||||
|
@field('extension') extension;
|
||||||
|
|
||||||
|
@date('_updated_at') _updatedAt;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import { field } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export default class FrequentlyUsedEmoji extends Model {
|
||||||
|
static table = 'frequently_used_emojis';
|
||||||
|
|
||||||
|
@field('content') content;
|
||||||
|
|
||||||
|
@field('extension') extension;
|
||||||
|
|
||||||
|
@field('is_custom') isCustom;
|
||||||
|
|
||||||
|
@field('count') count;
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import {
|
||||||
|
field, relation, date, json
|
||||||
|
} from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export default class Message extends Model {
|
||||||
|
static table = 'messages';
|
||||||
|
|
||||||
|
static associations = {
|
||||||
|
subscriptions: { type: 'belongs_to', key: 'rid' }
|
||||||
|
}
|
||||||
|
|
||||||
|
@field('msg') msg;
|
||||||
|
|
||||||
|
@field('t') t;
|
||||||
|
|
||||||
|
@date('ts') ts;
|
||||||
|
|
||||||
|
@json('u', sanitizer) u;
|
||||||
|
|
||||||
|
@relation('subscriptions', 'rid') subscription;
|
||||||
|
|
||||||
|
@field('alias') alias;
|
||||||
|
|
||||||
|
@json('parse_urls', sanitizer) parseUrls;
|
||||||
|
|
||||||
|
@field('groupable') groupable;
|
||||||
|
|
||||||
|
@field('avatar') avatar;
|
||||||
|
|
||||||
|
@json('attachments', sanitizer) attachments;
|
||||||
|
|
||||||
|
@json('urls', sanitizer) urls;
|
||||||
|
|
||||||
|
@date('_updated_at') _updatedAt;
|
||||||
|
|
||||||
|
@field('status') status;
|
||||||
|
|
||||||
|
@field('pinned') pinned;
|
||||||
|
|
||||||
|
@field('starred') starred;
|
||||||
|
|
||||||
|
@json('edited_by', sanitizer) editedBy;
|
||||||
|
|
||||||
|
@json('reactions', sanitizer) reactions;
|
||||||
|
|
||||||
|
@field('role') role;
|
||||||
|
|
||||||
|
@field('drid') drid;
|
||||||
|
|
||||||
|
@field('dcount') dcount;
|
||||||
|
|
||||||
|
@date('dlm') dlm;
|
||||||
|
|
||||||
|
@field('tmid') tmid;
|
||||||
|
|
||||||
|
@field('tcount') tcount;
|
||||||
|
|
||||||
|
@date('tlm') tlm;
|
||||||
|
|
||||||
|
@json('replies', sanitizer) replies;
|
||||||
|
|
||||||
|
@json('mentions', sanitizer) mentions;
|
||||||
|
|
||||||
|
@json('channels', sanitizer) channels;
|
||||||
|
|
||||||
|
@field('unread') unread;
|
||||||
|
|
||||||
|
@field('auto_translate') autoTranslate;
|
||||||
|
|
||||||
|
@json('translations', sanitizer) translations;
|
||||||
|
|
||||||
|
@field('tmsg') tmsg;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import { json, date } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export default class Permission extends Model {
|
||||||
|
static table = 'permissions';
|
||||||
|
|
||||||
|
@json('roles', sanitizer) roles;
|
||||||
|
|
||||||
|
@date('_updated_at') _updatedAt;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import { field } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export default class Role extends Model {
|
||||||
|
static table = 'roles';
|
||||||
|
|
||||||
|
@field('description') description;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import { field, json } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export default class Room extends Model {
|
||||||
|
static table = 'rooms';
|
||||||
|
|
||||||
|
@json('custom_fields', sanitizer) customFields;
|
||||||
|
|
||||||
|
@field('broadcast') broadcast;
|
||||||
|
|
||||||
|
@field('encrypted') encrypted;
|
||||||
|
|
||||||
|
@field('ro') ro;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import { field, date } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export default class Server extends Model {
|
||||||
|
static table = 'servers';
|
||||||
|
|
||||||
|
@field('name') name;
|
||||||
|
|
||||||
|
@field('icon_url') iconURL;
|
||||||
|
|
||||||
|
@field('use_real_name') useRealName;
|
||||||
|
|
||||||
|
@field('file_upload_media_type_white_list') FileUpload_MediaTypeWhiteList;
|
||||||
|
|
||||||
|
@field('file_upload_max_file_size') FileUpload_MaxFileSize;
|
||||||
|
|
||||||
|
@date('rooms_updated_at') roomsUpdatedAt;
|
||||||
|
|
||||||
|
@field('version') version;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import { field, date } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export default class Setting extends Model {
|
||||||
|
static table = 'settings';
|
||||||
|
|
||||||
|
@field('value_as_string') valueAsString;
|
||||||
|
|
||||||
|
@field('value_as_boolean') valueAsBoolean;
|
||||||
|
|
||||||
|
@field('value_as_number') valueAsNumber;
|
||||||
|
|
||||||
|
@date('_updated_at') _updatedAt;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import { field } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export default class SlashCommand extends Model {
|
||||||
|
static table = 'slash_commands';
|
||||||
|
|
||||||
|
@field('params') params;
|
||||||
|
|
||||||
|
@field('description') description;
|
||||||
|
|
||||||
|
@field('client_only') clientOnly;
|
||||||
|
|
||||||
|
@field('provides_preview') providesPreview;
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import {
|
||||||
|
field, date, json, children
|
||||||
|
} from '@nozbe/watermelondb/decorators';
|
||||||
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export default class Subscription extends Model {
|
||||||
|
static table = 'subscriptions';
|
||||||
|
|
||||||
|
static associations = {
|
||||||
|
messages: { type: 'has_many', foreignKey: 'rid' },
|
||||||
|
threads: { type: 'has_many', foreignKey: 'rid' },
|
||||||
|
thread_messages: { type: 'has_many', foreignKey: 'subscription_id' },
|
||||||
|
uploads: { type: 'has_many', foreignKey: 'rid' }
|
||||||
|
}
|
||||||
|
|
||||||
|
@field('_id') _id;
|
||||||
|
|
||||||
|
@field('f') f;
|
||||||
|
|
||||||
|
@field('t') t;
|
||||||
|
|
||||||
|
@date('ts') ts;
|
||||||
|
|
||||||
|
@date('ls') ls;
|
||||||
|
|
||||||
|
@field('name') name;
|
||||||
|
|
||||||
|
@field('fname') fname;
|
||||||
|
|
||||||
|
@field('rid') rid;
|
||||||
|
|
||||||
|
@field('open') open;
|
||||||
|
|
||||||
|
@field('alert') alert;
|
||||||
|
|
||||||
|
@json('roles', sanitizer) roles;
|
||||||
|
|
||||||
|
@field('unread') unread;
|
||||||
|
|
||||||
|
@field('user_mentions') userMentions;
|
||||||
|
|
||||||
|
@date('room_updated_at') roomUpdatedAt;
|
||||||
|
|
||||||
|
@field('ro') ro;
|
||||||
|
|
||||||
|
@date('last_open') lastOpen;
|
||||||
|
|
||||||
|
@field('description') description;
|
||||||
|
|
||||||
|
@field('announcement') announcement;
|
||||||
|
|
||||||
|
@field('topic') topic;
|
||||||
|
|
||||||
|
@field('blocked') blocked;
|
||||||
|
|
||||||
|
@field('blocker') blocker;
|
||||||
|
|
||||||
|
@field('react_when_read_only') reactWhenReadOnly;
|
||||||
|
|
||||||
|
@field('archived') archived;
|
||||||
|
|
||||||
|
@field('join_code_required') joinCodeRequired;
|
||||||
|
|
||||||
|
@field('notifications') notifications;
|
||||||
|
|
||||||
|
@json('muted', sanitizer) muted;
|
||||||
|
|
||||||
|
@field('broadcast') broadcast;
|
||||||
|
|
||||||
|
@field('prid') prid;
|
||||||
|
|
||||||
|
@field('draft_message') draftMessage;
|
||||||
|
|
||||||
|
@date('last_thread_sync') lastThreadSync;
|
||||||
|
|
||||||
|
@field('auto_translate') autoTranslate;
|
||||||
|
|
||||||
|
@field('auto_translate_language') autoTranslateLanguage;
|
||||||
|
|
||||||
|
@json('last_message', sanitizer) lastMessage;
|
||||||
|
|
||||||
|
@children('messages') messages;
|
||||||
|
|
||||||
|
@children('threads') threads;
|
||||||
|
|
||||||
|
@children('thread_messages') threadMessages;
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import {
|
||||||
|
field, relation, date, json
|
||||||
|
} from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export default class Thread extends Model {
|
||||||
|
static table = 'threads';
|
||||||
|
|
||||||
|
static associations = {
|
||||||
|
subscriptions: { type: 'belongs_to', key: 'rid' }
|
||||||
|
}
|
||||||
|
|
||||||
|
@field('msg') msg;
|
||||||
|
|
||||||
|
@field('t') t;
|
||||||
|
|
||||||
|
@date('ts') ts;
|
||||||
|
|
||||||
|
@json('u', sanitizer) u;
|
||||||
|
|
||||||
|
@relation('subscriptions', 'rid') subscription;
|
||||||
|
|
||||||
|
@field('alias') alias;
|
||||||
|
|
||||||
|
@json('parse_urls', sanitizer) parseUrls;
|
||||||
|
|
||||||
|
@field('groupable') groupable;
|
||||||
|
|
||||||
|
@field('avatar') avatar;
|
||||||
|
|
||||||
|
@json('attachments', sanitizer) attachments;
|
||||||
|
|
||||||
|
@json('urls', sanitizer) urls;
|
||||||
|
|
||||||
|
@date('_updated_at') _updatedAt;
|
||||||
|
|
||||||
|
@field('status') status;
|
||||||
|
|
||||||
|
@field('pinned') pinned;
|
||||||
|
|
||||||
|
@field('starred') starred;
|
||||||
|
|
||||||
|
@json('edited_by', sanitizer) editedBy;
|
||||||
|
|
||||||
|
@json('reactions', sanitizer) reactions;
|
||||||
|
|
||||||
|
@field('role') role;
|
||||||
|
|
||||||
|
@field('drid') drid;
|
||||||
|
|
||||||
|
@field('dcount') dcount;
|
||||||
|
|
||||||
|
@date('dlm') dlm;
|
||||||
|
|
||||||
|
@field('tmid') tmid;
|
||||||
|
|
||||||
|
@field('tcount') tcount;
|
||||||
|
|
||||||
|
@date('tlm') tlm;
|
||||||
|
|
||||||
|
@json('replies', sanitizer) replies;
|
||||||
|
|
||||||
|
@json('mentions', sanitizer) mentions;
|
||||||
|
|
||||||
|
@json('channels', sanitizer) channels;
|
||||||
|
|
||||||
|
@field('unread') unread;
|
||||||
|
|
||||||
|
@field('auto_translate') autoTranslate;
|
||||||
|
|
||||||
|
@json('translations', sanitizer) translations;
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import {
|
||||||
|
field, relation, date, json
|
||||||
|
} from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export default class ThreadMessage extends Model {
|
||||||
|
static table = 'thread_messages';
|
||||||
|
|
||||||
|
static associations = {
|
||||||
|
subscriptions: { type: 'belongs_to', key: 'subscription_id' }
|
||||||
|
}
|
||||||
|
|
||||||
|
@field('msg') msg;
|
||||||
|
|
||||||
|
@field('t') t;
|
||||||
|
|
||||||
|
@date('ts') ts;
|
||||||
|
|
||||||
|
@json('u', sanitizer) u;
|
||||||
|
|
||||||
|
@relation('subscriptions', 'subscription_id') subscription;
|
||||||
|
|
||||||
|
@field('rid') rid;
|
||||||
|
|
||||||
|
@field('alias') alias;
|
||||||
|
|
||||||
|
@json('parse_urls', sanitizer) parseUrls;
|
||||||
|
|
||||||
|
@field('groupable') groupable;
|
||||||
|
|
||||||
|
@field('avatar') avatar;
|
||||||
|
|
||||||
|
@json('attachments', sanitizer) attachments;
|
||||||
|
|
||||||
|
@json('urls', sanitizer) urls;
|
||||||
|
|
||||||
|
@date('_updated_at') _updatedAt;
|
||||||
|
|
||||||
|
@field('status') status;
|
||||||
|
|
||||||
|
@field('pinned') pinned;
|
||||||
|
|
||||||
|
@field('starred') starred;
|
||||||
|
|
||||||
|
@json('edited_by', sanitizer) editedBy;
|
||||||
|
|
||||||
|
@json('reactions', sanitizer) reactions;
|
||||||
|
|
||||||
|
@field('role') role;
|
||||||
|
|
||||||
|
@field('drid') drid;
|
||||||
|
|
||||||
|
@field('dcount') dcount;
|
||||||
|
|
||||||
|
@date('dlm') dlm;
|
||||||
|
|
||||||
|
@field('tcount') tcount;
|
||||||
|
|
||||||
|
@date('tlm') tlm;
|
||||||
|
|
||||||
|
@json('replies', sanitizer) replies;
|
||||||
|
|
||||||
|
@json('mentions', sanitizer) mentions;
|
||||||
|
|
||||||
|
@json('channels', sanitizer) channels;
|
||||||
|
|
||||||
|
@field('unread') unread;
|
||||||
|
|
||||||
|
@field('auto_translate') autoTranslate;
|
||||||
|
|
||||||
|
@json('translations', sanitizer) translations;
|
||||||
|
|
||||||
|
@field('draft_message') draftMessage;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import { field, relation } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
export default class Upload extends Model {
|
||||||
|
static table = 'uploads';
|
||||||
|
|
||||||
|
static associations = {
|
||||||
|
subscriptions: { type: 'belongs_to', key: 'rid' }
|
||||||
|
}
|
||||||
|
|
||||||
|
@field('path') path;
|
||||||
|
|
||||||
|
@relation('subscriptions', 'rid') subscription;
|
||||||
|
|
||||||
|
@field('name') name;
|
||||||
|
|
||||||
|
@field('description') description;
|
||||||
|
|
||||||
|
@field('size') size;
|
||||||
|
|
||||||
|
@field('type') type;
|
||||||
|
|
||||||
|
@field('store') store;
|
||||||
|
|
||||||
|
@field('progress') progress;
|
||||||
|
|
||||||
|
@field('error') error;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Model } from '@nozbe/watermelondb';
|
||||||
|
import { field, json } from '@nozbe/watermelondb/decorators';
|
||||||
|
|
||||||
|
import { sanitizer } from '../utils';
|
||||||
|
|
||||||
|
export default class User extends Model {
|
||||||
|
static table = 'users';
|
||||||
|
|
||||||
|
@field('token') token;
|
||||||
|
|
||||||
|
@field('username') username;
|
||||||
|
|
||||||
|
@field('name') name;
|
||||||
|
|
||||||
|
@field('language') language;
|
||||||
|
|
||||||
|
@field('status') status;
|
||||||
|
|
||||||
|
@json('roles', sanitizer) roles;
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
|
export default appSchema({
|
||||||
|
version: 1,
|
||||||
|
tables: [
|
||||||
|
tableSchema({
|
||||||
|
name: 'subscriptions',
|
||||||
|
columns: [
|
||||||
|
{ name: '_id', type: 'string' },
|
||||||
|
{ name: 'f', type: 'boolean' },
|
||||||
|
{ name: 't', type: 'string', isIndexed: true },
|
||||||
|
{ name: 'ts', type: 'number' },
|
||||||
|
{ name: 'ls', type: 'number' },
|
||||||
|
{ name: 'name', type: 'string', isIndexed: true },
|
||||||
|
{ name: 'fname', type: 'string' },
|
||||||
|
{ name: 'rid', type: 'string', isIndexed: true },
|
||||||
|
{ name: 'open', type: 'boolean' },
|
||||||
|
{ name: 'alert', type: 'boolean' },
|
||||||
|
{ name: 'roles', type: 'string', isOptional: true },
|
||||||
|
{ name: 'unread', type: 'number' },
|
||||||
|
{ name: 'user_mentions', type: 'number' },
|
||||||
|
{ name: 'room_updated_at', type: 'number' },
|
||||||
|
{ name: 'ro', type: 'boolean' },
|
||||||
|
{ name: 'last_open', type: 'number', isOptional: true },
|
||||||
|
{ name: 'last_message', type: 'string', isOptional: true },
|
||||||
|
{ name: 'description', type: 'string', isOptional: true },
|
||||||
|
{ name: 'announcement', type: 'string', isOptional: true },
|
||||||
|
{ name: 'topic', type: 'string', isOptional: true },
|
||||||
|
{ name: 'blocked', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'blocker', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'react_when_read_only', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'archived', type: 'boolean' },
|
||||||
|
{ name: 'join_code_required', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'muted', type: 'string', isOptional: true },
|
||||||
|
{ name: 'broadcast', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'prid', type: 'string', isOptional: true },
|
||||||
|
{ name: 'draft_message', type: 'string', isOptional: true },
|
||||||
|
{ name: 'last_thread_sync', type: 'number', isOptional: true },
|
||||||
|
{ name: 'auto_translate', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'auto_translate_language', type: 'string' }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'rooms',
|
||||||
|
columns: [
|
||||||
|
{ name: 'custom_fields', type: 'string' },
|
||||||
|
{ name: 'broadcast', type: 'boolean' },
|
||||||
|
{ name: 'encrypted', type: 'boolean' },
|
||||||
|
{ name: 'ro', type: 'boolean' }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'messages',
|
||||||
|
columns: [
|
||||||
|
{ name: 'msg', type: 'string', isOptional: true },
|
||||||
|
{ name: 't', type: 'string', isOptional: true },
|
||||||
|
{ name: 'rid', type: 'string', isIndexed: true },
|
||||||
|
{ name: 'ts', type: 'number' },
|
||||||
|
{ name: 'u', type: 'string' },
|
||||||
|
{ name: 'alias', type: 'string' },
|
||||||
|
{ name: 'parse_urls', type: 'string' },
|
||||||
|
{ name: 'groupable', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'avatar', type: 'string', isOptional: true },
|
||||||
|
{ name: 'attachments', type: 'string', isOptional: true },
|
||||||
|
{ name: 'urls', type: 'string', isOptional: true },
|
||||||
|
{ name: '_updated_at', type: 'number' },
|
||||||
|
{ name: 'status', type: 'number', isOptional: true },
|
||||||
|
{ name: 'pinned', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'starred', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'edited_by', type: 'string', isOptional: true },
|
||||||
|
{ name: 'reactions', type: 'string', isOptional: true },
|
||||||
|
{ name: 'role', type: 'string', isOptional: true },
|
||||||
|
{ name: 'drid', type: 'string', isOptional: true },
|
||||||
|
{ name: 'dcount', type: 'number', isOptional: true },
|
||||||
|
{ name: 'dlm', type: 'number', isOptional: true },
|
||||||
|
{ name: 'tmid', type: 'string', isOptional: true },
|
||||||
|
{ name: 'tcount', type: 'number', isOptional: true },
|
||||||
|
{ name: 'tlm', type: 'number', isOptional: true },
|
||||||
|
{ name: 'replies', type: 'string', isOptional: true },
|
||||||
|
{ name: 'mentions', type: 'string', isOptional: true },
|
||||||
|
{ name: 'channels', type: 'string', isOptional: true },
|
||||||
|
{ name: 'unread', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'auto_translate', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'translations', type: 'string', isOptional: true },
|
||||||
|
{ name: 'tmsg', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'threads',
|
||||||
|
columns: [
|
||||||
|
{ name: 'msg', type: 'string', isOptional: true },
|
||||||
|
{ name: 't', type: 'string', isOptional: true },
|
||||||
|
{ name: 'rid', type: 'string', isIndexed: true },
|
||||||
|
{ name: '_updated_at', type: 'number' },
|
||||||
|
{ name: 'ts', type: 'number' },
|
||||||
|
{ name: 'u', type: 'string' },
|
||||||
|
{ name: 'alias', type: 'string', isOptional: true },
|
||||||
|
{ name: 'parse_urls', type: 'string', isOptional: true },
|
||||||
|
{ name: 'groupable', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'avatar', type: 'string', isOptional: true },
|
||||||
|
{ name: 'attachments', type: 'string', isOptional: true },
|
||||||
|
{ name: 'urls', type: 'string', isOptional: true },
|
||||||
|
{ name: 'status', type: 'number', isOptional: true },
|
||||||
|
{ name: 'pinned', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'starred', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'edited_by', type: 'string', isOptional: true },
|
||||||
|
{ name: 'reactions', type: 'string', isOptional: true },
|
||||||
|
{ name: 'role', type: 'string', isOptional: true },
|
||||||
|
{ name: 'drid', type: 'string', isOptional: true },
|
||||||
|
{ name: 'dcount', type: 'number', isOptional: true },
|
||||||
|
{ name: 'dlm', type: 'number', isOptional: true },
|
||||||
|
{ name: 'tmid', type: 'string', isOptional: true },
|
||||||
|
{ name: 'tcount', type: 'number', isOptional: true },
|
||||||
|
{ name: 'tlm', type: 'number', isOptional: true },
|
||||||
|
{ name: 'replies', type: 'string', isOptional: true },
|
||||||
|
{ name: 'mentions', type: 'string', isOptional: true },
|
||||||
|
{ name: 'channels', type: 'string', isOptional: true },
|
||||||
|
{ name: 'unread', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'auto_translate', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'translations', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'thread_messages',
|
||||||
|
columns: [
|
||||||
|
{ name: 'msg', type: 'string', isOptional: true },
|
||||||
|
{ name: 't', type: 'string', isOptional: true },
|
||||||
|
{ name: 'rid', type: 'string', isIndexed: true },
|
||||||
|
{ name: 'subscription_id', type: 'string', isIndexed: true },
|
||||||
|
{ name: '_updated_at', type: 'number' },
|
||||||
|
{ name: 'ts', type: 'number' },
|
||||||
|
{ name: 'u', type: 'string' },
|
||||||
|
{ name: 'alias', type: 'string', isOptional: true },
|
||||||
|
{ name: 'parse_urls', type: 'string', isOptional: true },
|
||||||
|
{ name: 'groupable', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'avatar', type: 'string', isOptional: true },
|
||||||
|
{ name: 'attachments', type: 'string', isOptional: true },
|
||||||
|
{ name: 'urls', type: 'string', isOptional: true },
|
||||||
|
{ name: 'status', type: 'number', isOptional: true },
|
||||||
|
{ name: 'pinned', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'starred', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'edited_by', type: 'string', isOptional: true },
|
||||||
|
{ name: 'reactions', type: 'string', isOptional: true },
|
||||||
|
{ name: 'role', type: 'string', isOptional: true },
|
||||||
|
{ name: 'drid', type: 'string', isOptional: true },
|
||||||
|
{ name: 'dcount', type: 'number', isOptional: true },
|
||||||
|
{ name: 'dlm', type: 'number', isOptional: true },
|
||||||
|
{ name: 'tcount', type: 'number', isOptional: true },
|
||||||
|
{ name: 'tlm', type: 'number', isOptional: true },
|
||||||
|
{ name: 'replies', type: 'string', isOptional: true },
|
||||||
|
{ name: 'mentions', type: 'string', isOptional: true },
|
||||||
|
{ name: 'channels', type: 'string', isOptional: true },
|
||||||
|
{ name: 'unread', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'auto_translate', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'translations', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'custom_emojis',
|
||||||
|
columns: [
|
||||||
|
{ name: 'name', type: 'string', isOptional: true },
|
||||||
|
{ name: 'aliases', type: 'string', isOptional: true },
|
||||||
|
{ name: 'extension', type: 'string' },
|
||||||
|
{ name: '_updated_at', type: 'number' }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'frequently_used_emojis',
|
||||||
|
columns: [
|
||||||
|
{ name: 'content', type: 'string', isOptional: true },
|
||||||
|
{ name: 'extension', type: 'string', isOptional: true },
|
||||||
|
{ name: 'is_custom', type: 'boolean' },
|
||||||
|
{ name: 'count', type: 'number' }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'uploads',
|
||||||
|
columns: [
|
||||||
|
{ name: 'path', type: 'string', isOptional: true },
|
||||||
|
{ name: 'rid', type: 'string', isIndexed: true },
|
||||||
|
{ name: 'name', type: 'string', isOptional: true },
|
||||||
|
{ name: 'description', type: 'string', isOptional: true },
|
||||||
|
{ name: 'size', type: 'number' },
|
||||||
|
{ name: 'type', type: 'string', isOptional: true },
|
||||||
|
{ name: 'store', type: 'string', isOptional: true },
|
||||||
|
{ name: 'progress', type: 'number' },
|
||||||
|
{ name: 'error', type: 'boolean' }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'settings',
|
||||||
|
columns: [
|
||||||
|
{ name: 'value_as_string', type: 'string', isOptional: true },
|
||||||
|
{ name: 'value_as_boolean', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'value_as_number', type: 'number', isOptional: true },
|
||||||
|
{ name: '_updated_at', type: 'number', isOptional: true }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'roles',
|
||||||
|
columns: [
|
||||||
|
{ name: 'description', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'permissions',
|
||||||
|
columns: [
|
||||||
|
{ name: 'roles', type: 'string' },
|
||||||
|
{ name: '_updated_at', type: 'number', isOptional: true }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'slash_commands',
|
||||||
|
columns: [
|
||||||
|
{ name: 'params', type: 'string', isOptional: true },
|
||||||
|
{ name: 'description', type: 'string', isOptional: true },
|
||||||
|
{ name: 'client_only', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'provides_preview', type: 'boolean', isOptional: true }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
|
export default appSchema({
|
||||||
|
version: 2,
|
||||||
|
tables: [
|
||||||
|
tableSchema({
|
||||||
|
name: 'users',
|
||||||
|
columns: [
|
||||||
|
{ name: 'token', type: 'string', isOptional: true },
|
||||||
|
{ name: 'username', type: 'string', isOptional: true },
|
||||||
|
{ name: 'name', type: 'string', isOptional: true },
|
||||||
|
{ name: 'language', type: 'string', isOptional: true },
|
||||||
|
{ name: 'status', type: 'string', isOptional: true },
|
||||||
|
{ name: 'roles', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
tableSchema({
|
||||||
|
name: 'servers',
|
||||||
|
columns: [
|
||||||
|
{ name: 'name', type: 'string', isOptional: true },
|
||||||
|
{ name: 'icon_url', type: 'string', isOptional: true },
|
||||||
|
{ name: 'use_real_name', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'file_upload_media_type_white_list', type: 'string', isOptional: true },
|
||||||
|
{ name: 'file_upload_max_file_size', type: 'number', isOptional: true },
|
||||||
|
{ name: 'rooms_updated_at', type: 'number', isOptional: true },
|
||||||
|
{ name: 'version', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
|
@ -0,0 +1 @@
|
||||||
|
export const sanitizer = r => r;
|
|
@ -1,4 +1,4 @@
|
||||||
import database from '../realm';
|
import database from '../database';
|
||||||
|
|
||||||
const restTypes = {
|
const restTypes = {
|
||||||
channel: 'channels', direct: 'im', group: 'groups'
|
channel: 'channels', direct: 'im', group: 'groups'
|
||||||
|
@ -18,14 +18,19 @@ async function open({ type, rid }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function canOpenRoom({ rid, path }) {
|
export default async function canOpenRoom({ rid, path }) {
|
||||||
|
try {
|
||||||
|
const db = database.active;
|
||||||
|
const subsCollection = db.collections.get('subscriptions');
|
||||||
const [type] = path.split('/');
|
const [type] = path.split('/');
|
||||||
if (type === 'channel') {
|
if (type === 'channel') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const room = database.objects('subscriptions').filtered('rid == $0', rid);
|
try {
|
||||||
if (room.length) {
|
await subsCollection.find(rid);
|
||||||
return true;
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -33,4 +38,7 @@ export default async function canOpenRoom({ rid, path }) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,110 @@
|
||||||
import { InteractionManager } from 'react-native';
|
import { InteractionManager } from 'react-native';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
import orderBy from 'lodash/orderBy';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
|
||||||
import reduxStore from '../createStore';
|
import reduxStore from '../createStore';
|
||||||
import database from '../realm';
|
import database from '../database';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
import { setCustomEmojis as setCustomEmojisAction } from '../../actions/customEmojis';
|
||||||
|
|
||||||
const getUpdatedSince = () => {
|
const getUpdatedSince = (allEmojis) => {
|
||||||
const emoji = database.objects('customEmojis').sorted('_updatedAt', true)[0];
|
if (!allEmojis.length) {
|
||||||
return emoji && emoji._updatedAt.toISOString();
|
return null;
|
||||||
|
}
|
||||||
|
const ordered = orderBy(allEmojis.filter(item => item._updatedAt !== null), ['_updatedAt'], ['desc']);
|
||||||
|
return ordered && ordered[0]._updatedAt.toISOString();
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = (customEmojis) => {
|
const updateEmojis = async({ update = [], remove = [], allRecords }) => {
|
||||||
if (customEmojis && customEmojis.length) {
|
if (!((update && update.length) || (remove && remove.length))) {
|
||||||
customEmojis.forEach((emoji) => {
|
return;
|
||||||
try {
|
|
||||||
database.create('customEmojis', emoji, true);
|
|
||||||
} catch (e) {
|
|
||||||
// log('getEmojis create', e);
|
|
||||||
}
|
}
|
||||||
|
const db = database.active;
|
||||||
|
const emojisCollection = db.collections.get('custom_emojis');
|
||||||
|
let emojisToCreate = [];
|
||||||
|
let emojisToUpdate = [];
|
||||||
|
let emojisToDelete = [];
|
||||||
|
|
||||||
|
// Create or update
|
||||||
|
if (update && update.length) {
|
||||||
|
emojisToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id));
|
||||||
|
emojisToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id));
|
||||||
|
emojisToCreate = emojisToCreate.map(emoji => emojisCollection.prepareCreate((e) => {
|
||||||
|
e._raw = sanitizedRaw({ id: emoji._id }, emojisCollection.schema);
|
||||||
|
Object.assign(e, emoji);
|
||||||
|
}));
|
||||||
|
emojisToUpdate = emojisToUpdate.map((emoji) => {
|
||||||
|
const newEmoji = update.find(e => e._id === emoji.id);
|
||||||
|
return emoji.prepareUpdate((e) => {
|
||||||
|
Object.assign(e, newEmoji);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (remove && remove.length) {
|
||||||
|
emojisToDelete = allRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
|
||||||
|
emojisToDelete = emojisToDelete.map(emoji => emoji.prepareDestroyPermanently());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.action(async() => {
|
||||||
|
await db.batch(
|
||||||
|
...emojisToCreate,
|
||||||
|
...emojisToUpdate,
|
||||||
|
...emojisToDelete
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function setCustomEmojis() {
|
||||||
|
const db = database.active;
|
||||||
|
const emojisCollection = db.collections.get('custom_emojis');
|
||||||
|
const allEmojis = await emojisCollection.query().fetch();
|
||||||
|
const parsed = allEmojis.reduce((ret, item) => {
|
||||||
|
ret[item.name] = {
|
||||||
|
name: item.name,
|
||||||
|
extension: item.extension
|
||||||
|
};
|
||||||
|
item.aliases.forEach((alias) => {
|
||||||
|
ret[alias] = {
|
||||||
|
name: item.name,
|
||||||
|
extension: item.extension
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
}, {});
|
||||||
|
reduxStore.dispatch(setCustomEmojisAction(parsed));
|
||||||
|
}
|
||||||
|
|
||||||
export default function() {
|
export function getCustomEmojis() {
|
||||||
return new Promise(async(resolve) => {
|
return new Promise(async(resolve) => {
|
||||||
try {
|
try {
|
||||||
const serverVersion = reduxStore.getState().server.version;
|
const serverVersion = reduxStore.getState().server.version;
|
||||||
const updatedSince = getUpdatedSince();
|
const db = database.active;
|
||||||
|
const emojisCollection = db.collections.get('custom_emojis');
|
||||||
|
const allRecords = await emojisCollection.query().fetch();
|
||||||
|
const updatedSince = await getUpdatedSince(allRecords);
|
||||||
|
|
||||||
// if server version is lower than 0.75.0, fetches from old api
|
// if server version is lower than 0.75.0, fetches from old api
|
||||||
if (semver.lt(serverVersion, '0.75.0')) {
|
if (semver.lt(serverVersion, '0.75.0')) {
|
||||||
// RC 0.61.0
|
// RC 0.61.0
|
||||||
const result = await this.sdk.get('emoji-custom');
|
const result = await this.sdk.get('emoji-custom');
|
||||||
|
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
let { emojis } = result;
|
let { emojis } = result;
|
||||||
emojis = emojis.filter(emoji => !updatedSince || emoji._updatedAt > updatedSince);
|
emojis = emojis.filter(emoji => !updatedSince || emoji._updatedAt > updatedSince);
|
||||||
database.write(() => {
|
const changedEmojis = await updateEmojis({ update: emojis, allRecords });
|
||||||
create(emojis);
|
|
||||||
});
|
// `setCustomEmojis` is fired on selectServer
|
||||||
|
// We run it again only if emojis were changed
|
||||||
|
if (changedEmojis) {
|
||||||
|
setCustomEmojis();
|
||||||
|
}
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -55,27 +120,18 @@ export default function() {
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractionManager.runAfterInteractions(
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
() => database.write(() => {
|
|
||||||
const { emojis } = result;
|
const { emojis } = result;
|
||||||
create(emojis.update);
|
const { update, remove } = emojis;
|
||||||
|
const changedEmojis = await updateEmojis({ update, remove, allRecords });
|
||||||
|
|
||||||
if (emojis.delete && emojis.delete.length) {
|
// `setCustomEmojis` is fired on selectServer
|
||||||
emojis.delete.forEach((emoji) => {
|
// We run it again only if emojis were changed
|
||||||
try {
|
if (changedEmojis) {
|
||||||
const emojiRecord = database.objectForPrimaryKey('customEmojis', emoji._id);
|
setCustomEmojis();
|
||||||
if (emojiRecord) {
|
|
||||||
database.delete(emojiRecord);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return resolve();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
return resolve();
|
return resolve();
|
||||||
|
|
|
@ -1,31 +1,82 @@
|
||||||
import { InteractionManager } from 'react-native';
|
import { InteractionManager } from 'react-native';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
import { orderBy } from 'lodash';
|
||||||
|
|
||||||
import database from '../realm';
|
import database from '../database';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import reduxStore from '../createStore';
|
import reduxStore from '../createStore';
|
||||||
|
import protectedFunction from './helpers/protectedFunction';
|
||||||
|
|
||||||
const getUpdatedSince = () => {
|
const getUpdatedSince = (allRecords) => {
|
||||||
const permissions = database.objects('permissions').sorted('_updatedAt', true)[0];
|
|
||||||
return permissions && permissions._updatedAt.toISOString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const create = (permissions) => {
|
|
||||||
if (permissions && permissions.length) {
|
|
||||||
permissions.forEach((permission) => {
|
|
||||||
try {
|
try {
|
||||||
database.create('permissions', permission, true);
|
if (!allRecords.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const ordered = orderBy(allRecords.filter(item => item._updatedAt !== null), ['_updatedAt'], ['desc']);
|
||||||
|
return ordered && ordered[0]._updatedAt.toISOString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePermissions = async({ update = [], remove = [], allRecords }) => {
|
||||||
|
if (!((update && update.length) || (remove && remove.length))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const db = database.active;
|
||||||
|
const permissionsCollection = db.collections.get('permissions');
|
||||||
|
|
||||||
|
// filter permissions
|
||||||
|
let permissionsToCreate = [];
|
||||||
|
let permissionsToUpdate = [];
|
||||||
|
let permissionsToDelete = [];
|
||||||
|
|
||||||
|
// Create or update
|
||||||
|
if (update && update.length) {
|
||||||
|
permissionsToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id));
|
||||||
|
permissionsToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id));
|
||||||
|
permissionsToCreate = permissionsToCreate.map(permission => permissionsCollection.prepareCreate(protectedFunction((p) => {
|
||||||
|
p._raw = sanitizedRaw({ id: permission._id }, permissionsCollection.schema);
|
||||||
|
Object.assign(p, permission);
|
||||||
|
})));
|
||||||
|
permissionsToUpdate = permissionsToUpdate.map((permission) => {
|
||||||
|
const newPermission = update.find(p => p._id === permission.id);
|
||||||
|
return permission.prepareUpdate(protectedFunction((p) => {
|
||||||
|
Object.assign(p, newPermission);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
if (remove && remove.length) {
|
||||||
|
permissionsToDelete = allRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
|
||||||
|
permissionsToDelete = permissionsToDelete.map(permission => permission.prepareDestroyPermanently());
|
||||||
|
}
|
||||||
|
|
||||||
|
const batch = [
|
||||||
|
...permissionsToCreate,
|
||||||
|
...permissionsToUpdate,
|
||||||
|
...permissionsToDelete
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.action(async() => {
|
||||||
|
await db.batch(...batch);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
return new Promise(async(resolve) => {
|
return new Promise(async(resolve) => {
|
||||||
try {
|
try {
|
||||||
const serverVersion = reduxStore.getState().server.version;
|
const serverVersion = reduxStore.getState().server.version;
|
||||||
|
const db = database.active;
|
||||||
|
const permissionsCollection = db.collections.get('permissions');
|
||||||
|
const allRecords = await permissionsCollection.query().fetch();
|
||||||
|
|
||||||
// if server version is lower than 0.73.0, fetches from old api
|
// if server version is lower than 0.73.0, fetches from old api
|
||||||
if (semver.lt(serverVersion, '0.73.0')) {
|
if (semver.lt(serverVersion, '0.73.0')) {
|
||||||
|
@ -34,15 +85,13 @@ export default function() {
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
database.write(() => {
|
await updatePermissions({ update: result.permissions, allRecords });
|
||||||
create(result.permissions);
|
|
||||||
});
|
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const params = {};
|
const params = {};
|
||||||
const updatedSince = getUpdatedSince();
|
const updatedSince = await getUpdatedSince(allRecords);
|
||||||
if (updatedSince) {
|
if (updatedSince) {
|
||||||
params.updatedSince = updatedSince;
|
params.updatedSince = updatedSince;
|
||||||
}
|
}
|
||||||
|
@ -53,25 +102,10 @@ export default function() {
|
||||||
return resolve();
|
return resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractionManager.runAfterInteractions(
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
() => database.write(() => {
|
await updatePermissions({ update: result.update, remove: result.delete, allRecords });
|
||||||
create(result.update);
|
|
||||||
|
|
||||||
if (result.delete && result.delete.length) {
|
|
||||||
result.delete.forEach((p) => {
|
|
||||||
try {
|
|
||||||
const permission = database.objectForPrimaryKey('permissions', p._id);
|
|
||||||
if (permission) {
|
|
||||||
database.delete(permission);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return resolve();
|
return resolve();
|
||||||
})
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { InteractionManager } from 'react-native';
|
import { InteractionManager } from 'react-native';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
|
||||||
import database from '../realm';
|
import database from '../database';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
import protectedFunction from './helpers/protectedFunction';
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
|
const db = database.active;
|
||||||
return new Promise(async(resolve) => {
|
return new Promise(async(resolve) => {
|
||||||
try {
|
try {
|
||||||
// RC 0.70.0
|
// RC 0.70.0
|
||||||
|
@ -16,14 +19,41 @@ export default function() {
|
||||||
const { roles } = result;
|
const { roles } = result;
|
||||||
|
|
||||||
if (roles && roles.length) {
|
if (roles && roles.length) {
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
database.write(() => roles.forEach((role) => {
|
await db.action(async() => {
|
||||||
try {
|
const rolesCollections = db.collections.get('roles');
|
||||||
database.create('roles', role, true);
|
const allRolesRecords = await rolesCollections.query().fetch();
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
// filter roles
|
||||||
}
|
let rolesToCreate = roles.filter(i1 => !allRolesRecords.find(i2 => i1._id === i2.id));
|
||||||
|
let rolesToUpdate = allRolesRecords.filter(i1 => roles.find(i2 => i1.id === i2._id));
|
||||||
|
|
||||||
|
// Create
|
||||||
|
rolesToCreate = rolesToCreate.map(role => rolesCollections.prepareCreate(protectedFunction((r) => {
|
||||||
|
r._raw = sanitizedRaw({ id: role._id }, rolesCollections.schema);
|
||||||
|
Object.assign(r, role);
|
||||||
|
})));
|
||||||
|
|
||||||
|
// Update
|
||||||
|
rolesToUpdate = rolesToUpdate.map((role) => {
|
||||||
|
const newRole = roles.find(r => r._id === role.id);
|
||||||
|
return role.prepareUpdate(protectedFunction((r) => {
|
||||||
|
Object.assign(r, newRole);
|
||||||
}));
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const allRecords = [
|
||||||
|
...rolesToCreate,
|
||||||
|
...rolesToUpdate
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.batch(...allRecords);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
return allRecords.length;
|
||||||
|
});
|
||||||
return resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,58 @@
|
||||||
import { InteractionManager } from 'react-native';
|
import { InteractionManager } from 'react-native';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
import reduxStore from '../createStore';
|
import reduxStore from '../createStore';
|
||||||
import database from '../realm';
|
|
||||||
import * as actions from '../../actions';
|
import * as actions from '../../actions';
|
||||||
import log from '../../utils/log';
|
|
||||||
import settings from '../../constants/settings';
|
import settings from '../../constants/settings';
|
||||||
|
import log from '../../utils/log';
|
||||||
|
import database from '../database';
|
||||||
|
import protectedFunction from './helpers/protectedFunction';
|
||||||
|
|
||||||
function updateServer(param) {
|
const serverInfoKeys = ['Site_Name', 'UI_Use_Real_Name', 'FileUpload_MediaTypeWhiteList', 'FileUpload_MaxFileSize'];
|
||||||
database.databases.serversDB.write(() => {
|
|
||||||
|
const serverInfoUpdate = async(serverInfo, iconSetting) => {
|
||||||
|
const serversDB = database.servers;
|
||||||
|
const serverId = reduxStore.getState().server.server;
|
||||||
|
|
||||||
|
let info = serverInfo.reduce((allSettings, setting) => {
|
||||||
|
if (setting._id === 'Site_Name') {
|
||||||
|
return { ...allSettings, name: setting.valueAsString };
|
||||||
|
}
|
||||||
|
if (setting._id === 'UI_Use_Real_Name') {
|
||||||
|
return { ...allSettings, useRealName: setting.valueAsBoolean };
|
||||||
|
}
|
||||||
|
if (setting._id === 'FileUpload_MediaTypeWhiteList') {
|
||||||
|
return { ...allSettings, FileUpload_MediaTypeWhiteList: setting.valueAsString };
|
||||||
|
}
|
||||||
|
if (setting._id === 'FileUpload_MaxFileSize') {
|
||||||
|
return { ...allSettings, FileUpload_MaxFileSize: setting.valueAsNumber };
|
||||||
|
}
|
||||||
|
return allSettings;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
if (iconSetting) {
|
||||||
|
const iconURL = `${ serverId }/${ iconSetting.value.url || iconSetting.value.defaultUrl }`;
|
||||||
|
info = { ...info, iconURL };
|
||||||
|
}
|
||||||
|
|
||||||
|
await serversDB.action(async() => {
|
||||||
try {
|
try {
|
||||||
database.databases.serversDB.create('servers', { id: reduxStore.getState().server.server, ...param }, true);
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
|
const server = await serversCollection.find(serverId);
|
||||||
|
|
||||||
|
await server.update((record) => {
|
||||||
|
Object.assign(record, info);
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export default async function() {
|
export default async function() {
|
||||||
try {
|
try {
|
||||||
|
const db = database.active;
|
||||||
const settingsParams = JSON.stringify(Object.keys(settings));
|
const settingsParams = JSON.stringify(Object.keys(settings));
|
||||||
// RC 0.60.0
|
// RC 0.60.0
|
||||||
const result = await fetch(`${ this.sdk.client.host }/api/v1/settings.public?query={"_id":{"$in":${ settingsParams }}}`).then(response => response.json());
|
const result = await fetch(`${ this.sdk.client.host }/api/v1/settings.public?query={"_id":{"$in":${ settingsParams }}}`).then(response => response.json());
|
||||||
|
@ -27,39 +62,52 @@ export default async function() {
|
||||||
}
|
}
|
||||||
const data = result.settings || [];
|
const data = result.settings || [];
|
||||||
const filteredSettings = this._prepareSettings(data.filter(item => item._id !== 'Assets_favicon_512'));
|
const filteredSettings = this._prepareSettings(data.filter(item => item._id !== 'Assets_favicon_512'));
|
||||||
|
const filteredSettingsIds = filteredSettings.map(s => s._id);
|
||||||
|
|
||||||
InteractionManager.runAfterInteractions(
|
|
||||||
() => database.write(
|
|
||||||
() => filteredSettings.forEach((setting) => {
|
|
||||||
try {
|
|
||||||
database.create('settings', { ...setting, _updatedAt: new Date() }, true);
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setting._id === 'Site_Name') {
|
|
||||||
updateServer.call(this, { name: setting.valueAsString });
|
|
||||||
}
|
|
||||||
if (setting._id === 'UI_Use_Real_Name') {
|
|
||||||
updateServer.call(this, { useRealName: setting.valueAsBoolean });
|
|
||||||
}
|
|
||||||
if (setting._id === 'FileUpload_MediaTypeWhiteList') {
|
|
||||||
updateServer.call(this, { FileUpload_MediaTypeWhiteList: setting.valueAsString });
|
|
||||||
}
|
|
||||||
if (setting._id === 'FileUpload_MaxFileSize') {
|
|
||||||
updateServer.call(this, { FileUpload_MaxFileSize: setting.valueAsNumber });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
reduxStore.dispatch(actions.addSettings(this.parseSettings(filteredSettings)));
|
reduxStore.dispatch(actions.addSettings(this.parseSettings(filteredSettings)));
|
||||||
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
|
// filter server info
|
||||||
|
const serverInfo = filteredSettings.filter(i1 => serverInfoKeys.includes(i1._id));
|
||||||
const iconSetting = data.find(item => item._id === 'Assets_favicon_512');
|
const iconSetting = data.find(item => item._id === 'Assets_favicon_512');
|
||||||
if (iconSetting) {
|
await serverInfoUpdate(serverInfo, iconSetting);
|
||||||
const baseUrl = reduxStore.getState().server.server;
|
|
||||||
const iconURL = `${ baseUrl }/${ iconSetting.value.url || iconSetting.value.defaultUrl }`;
|
await db.action(async() => {
|
||||||
updateServer.call(this, { iconURL });
|
const settingsCollection = db.collections.get('settings');
|
||||||
|
const allSettingsRecords = await settingsCollection
|
||||||
|
.query(Q.where('id', Q.oneOf(filteredSettingsIds)))
|
||||||
|
.fetch();
|
||||||
|
|
||||||
|
// filter settings
|
||||||
|
let settingsToCreate = filteredSettings.filter(i1 => !allSettingsRecords.find(i2 => i1._id === i2.id));
|
||||||
|
let settingsToUpdate = allSettingsRecords.filter(i1 => filteredSettings.find(i2 => i1.id === i2._id));
|
||||||
|
|
||||||
|
// Create
|
||||||
|
settingsToCreate = settingsToCreate.map(setting => settingsCollection.prepareCreate(protectedFunction((s) => {
|
||||||
|
s._raw = sanitizedRaw({ id: setting._id }, settingsCollection.schema);
|
||||||
|
Object.assign(s, setting);
|
||||||
|
})));
|
||||||
|
|
||||||
|
// Update
|
||||||
|
settingsToUpdate = settingsToUpdate.map((setting) => {
|
||||||
|
const newSetting = filteredSettings.find(s => s._id === setting.id);
|
||||||
|
return setting.prepareUpdate(protectedFunction((s) => {
|
||||||
|
Object.assign(s, newSetting);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const allRecords = [
|
||||||
|
...settingsToCreate,
|
||||||
|
...settingsToUpdate
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.batch(...allRecords);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
}
|
}
|
||||||
|
return allRecords.length;
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { InteractionManager } from 'react-native';
|
import { InteractionManager } from 'react-native';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
|
||||||
import database from '../realm';
|
import database from '../database';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
import protectedFunction from './helpers/protectedFunction';
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
|
const db = database.active;
|
||||||
return new Promise(async(resolve) => {
|
return new Promise(async(resolve) => {
|
||||||
try {
|
try {
|
||||||
// RC 0.60.2
|
// RC 0.60.2
|
||||||
|
@ -17,15 +20,41 @@ export default function() {
|
||||||
const { commands } = result;
|
const { commands } = result;
|
||||||
|
|
||||||
if (commands && commands.length) {
|
if (commands && commands.length) {
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
database.write(() => commands.forEach((command) => {
|
await db.action(async() => {
|
||||||
try {
|
const slashCommandsCollection = db.collections.get('slash_commands');
|
||||||
database.create('slashCommand', command, true);
|
const allSlashCommandsRecords = await slashCommandsCollection.query().fetch();
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
// filter slash commands
|
||||||
}
|
let slashCommandsToCreate = commands.filter(i1 => !allSlashCommandsRecords.find(i2 => i1.command === i2.id));
|
||||||
|
let slashCommandsToUpdate = allSlashCommandsRecords.filter(i1 => commands.find(i2 => i1.id === i2.command));
|
||||||
|
|
||||||
|
// Create
|
||||||
|
slashCommandsToCreate = slashCommandsToCreate.map(command => slashCommandsCollection.prepareCreate(protectedFunction((s) => {
|
||||||
|
s._raw = sanitizedRaw({ id: command.command }, slashCommandsCollection.schema);
|
||||||
|
Object.assign(s, command);
|
||||||
|
})));
|
||||||
|
|
||||||
|
// Update
|
||||||
|
slashCommandsToUpdate = slashCommandsToUpdate.map((command) => {
|
||||||
|
const newCommand = commands.find(s => s.command === command.id);
|
||||||
|
return command.prepareUpdate(protectedFunction((s) => {
|
||||||
|
Object.assign(s, newCommand);
|
||||||
}));
|
}));
|
||||||
return resolve();
|
});
|
||||||
|
|
||||||
|
const allRecords = [
|
||||||
|
...slashCommandsToCreate,
|
||||||
|
...slashCommandsToUpdate
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.batch(...allRecords);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
return allRecords.length;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -11,18 +11,17 @@ export const merge = (subscription, room) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (room) {
|
if (room) {
|
||||||
if (room.rid) {
|
if (room._updatedAt) {
|
||||||
subscription.rid = room.rid;
|
|
||||||
}
|
|
||||||
subscription.roomUpdatedAt = room._updatedAt;
|
subscription.roomUpdatedAt = room._updatedAt;
|
||||||
subscription.lastMessage = normalizeMessage(room.lastMessage);
|
subscription.lastMessage = normalizeMessage(room.lastMessage);
|
||||||
subscription.ro = room.ro;
|
|
||||||
subscription.description = room.description;
|
subscription.description = room.description;
|
||||||
subscription.topic = room.topic;
|
subscription.topic = room.topic;
|
||||||
subscription.announcement = room.announcement;
|
subscription.announcement = room.announcement;
|
||||||
subscription.reactWhenReadOnly = room.reactWhenReadOnly;
|
subscription.reactWhenReadOnly = room.reactWhenReadOnly;
|
||||||
subscription.archived = room.archived;
|
subscription.archived = room.archived || false;
|
||||||
subscription.joinCodeRequired = room.joinCodeRequired;
|
subscription.joinCodeRequired = room.joinCodeRequired;
|
||||||
|
}
|
||||||
|
subscription.ro = room.ro;
|
||||||
subscription.broadcast = room.broadcast;
|
subscription.broadcast = room.broadcast;
|
||||||
if (!subscription.roles || !subscription.roles.length) {
|
if (!subscription.roles || !subscription.roles.length) {
|
||||||
subscription.roles = [];
|
subscription.roles = [];
|
||||||
|
|
|
@ -2,12 +2,6 @@ export default fn => (...params) => {
|
||||||
try {
|
try {
|
||||||
fn(...params);
|
fn(...params);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let error = e;
|
console.log(e);
|
||||||
if (typeof error !== 'object') {
|
|
||||||
error = { error };
|
|
||||||
}
|
|
||||||
if (__DEV__) {
|
|
||||||
alert(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,24 +1,9 @@
|
||||||
import { InteractionManager } from 'react-native';
|
import { InteractionManager } from 'react-native';
|
||||||
|
|
||||||
import buildMessage from './helpers/buildMessage';
|
|
||||||
import database from '../realm';
|
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
import updateMessages from './updateMessages';
|
||||||
|
|
||||||
async function load({ rid: roomId, latest, t }) {
|
async function load({ rid: roomId, latest, t }) {
|
||||||
if (t === 'l') {
|
|
||||||
try {
|
|
||||||
// RC 0.51.0
|
|
||||||
const data = await this.sdk.methodCall('loadHistory', roomId, null, 50, latest);
|
|
||||||
if (!data || data.status === 'error') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return data.messages;
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let params = { roomId, count: 50 };
|
let params = { roomId, count: 50 };
|
||||||
if (latest) {
|
if (latest) {
|
||||||
params = { ...params, latest: new Date(latest).toISOString() };
|
params = { ...params, latest: new Date(latest).toISOString() };
|
||||||
|
@ -31,30 +16,14 @@ async function load({ rid: roomId, latest, t }) {
|
||||||
return data.messages;
|
return data.messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function loadMessagesForRoom(...args) {
|
export default function loadMessagesForRoom(args) {
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise(async(resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const data = await load.call(this, ...args);
|
const data = await load.call(this, args);
|
||||||
|
|
||||||
if (data && data.length) {
|
if (data && data.length) {
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
database.write(() => data.forEach((message) => {
|
await updateMessages({ rid: args.rid, update: data });
|
||||||
message = buildMessage(message);
|
|
||||||
try {
|
|
||||||
database.create('messages', message, true);
|
|
||||||
// if it's a thread "header"
|
|
||||||
if (message.tlm) {
|
|
||||||
database.create('threads', message, true);
|
|
||||||
}
|
|
||||||
// if it belongs to a thread
|
|
||||||
if (message.tmid) {
|
|
||||||
message.rid = message.tmid;
|
|
||||||
database.create('threadMessages', message, true);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
return resolve(data);
|
return resolve(data);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
import { InteractionManager } from 'react-native';
|
import { InteractionManager } from 'react-native';
|
||||||
|
|
||||||
import buildMessage from './helpers/buildMessage';
|
import database from '../database';
|
||||||
import database from '../realm';
|
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
import updateMessages from './updateMessages';
|
||||||
|
|
||||||
const getLastUpdate = (rid) => {
|
const getLastUpdate = async(rid) => {
|
||||||
const sub = database
|
try {
|
||||||
.objects('subscriptions')
|
const db = database.active;
|
||||||
.filtered('rid == $0', rid)[0];
|
const subsCollection = db.collections.get('subscriptions');
|
||||||
return sub && new Date(sub.lastOpen).toISOString();
|
const sub = await subsCollection.find(rid);
|
||||||
|
return sub.lastOpen.toISOString();
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function load({ rid: roomId, lastOpen }) {
|
async function load({ rid: roomId, lastOpen }) {
|
||||||
|
@ -16,60 +21,24 @@ async function load({ rid: roomId, lastOpen }) {
|
||||||
if (lastOpen) {
|
if (lastOpen) {
|
||||||
lastUpdate = new Date(lastOpen).toISOString();
|
lastUpdate = new Date(lastOpen).toISOString();
|
||||||
} else {
|
} else {
|
||||||
lastUpdate = getLastUpdate(roomId);
|
lastUpdate = await getLastUpdate(roomId);
|
||||||
}
|
}
|
||||||
// RC 0.60.0
|
// RC 0.60.0
|
||||||
const { result } = await this.sdk.get('chat.syncMessages', { roomId, lastUpdate });
|
const { result } = await this.sdk.get('chat.syncMessages', { roomId, lastUpdate });
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function loadMissedMessages(...args) {
|
export default function loadMissedMessages(args) {
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise(async(resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const data = (await load.call(this, ...args));
|
const data = (await load.call(this, { rid: args.rid, lastOpen: args.lastOpen }));
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
if (data.updated && data.updated.length) {
|
const { updated, deleted } = data;
|
||||||
const { updated } = data;
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
InteractionManager.runAfterInteractions(() => {
|
await updateMessages({ rid: args.rid, update: updated, remove: deleted });
|
||||||
database.write(() => updated.forEach((message) => {
|
|
||||||
try {
|
|
||||||
message = buildMessage(message);
|
|
||||||
database.create('messages', message, true);
|
|
||||||
// if it's a thread "header"
|
|
||||||
if (message.tlm) {
|
|
||||||
database.create('threads', message, true);
|
|
||||||
}
|
|
||||||
if (message.tmid) {
|
|
||||||
message.rid = message.tmid;
|
|
||||||
database.create('threadMessages', message, true);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (data.deleted && data.deleted.length) {
|
|
||||||
const { deleted } = data;
|
|
||||||
InteractionManager.runAfterInteractions(() => {
|
|
||||||
try {
|
|
||||||
database.write(() => {
|
|
||||||
deleted.forEach((m) => {
|
|
||||||
const message = database.objects('messages').filtered('_id = $0', m._id);
|
|
||||||
database.delete(message);
|
|
||||||
const thread = database.objects('threads').filtered('_id = $0', m._id);
|
|
||||||
database.delete(thread);
|
|
||||||
const threadMessage = database.objects('threadMessages').filtered('_id = $0', m._id);
|
|
||||||
database.delete(threadMessage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve();
|
resolve();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { InteractionManager } from 'react-native';
|
import { InteractionManager } from 'react-native';
|
||||||
import EJSON from 'ejson';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
|
||||||
import buildMessage from './helpers/buildMessage';
|
import buildMessage from './helpers/buildMessage';
|
||||||
import database from '../realm';
|
import database from '../database';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
import protectedFunction from './helpers/protectedFunction';
|
||||||
|
|
||||||
async function load({ tmid, offset }) {
|
async function load({ tmid, offset }) {
|
||||||
try {
|
try {
|
||||||
|
@ -21,29 +23,53 @@ async function load({ tmid, offset }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function loadThreadMessages({ tmid, offset = 0 }) {
|
export default function loadThreadMessages({ tmid, rid, offset = 0 }) {
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise(async(resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const data = await load.call(this, { tmid, offset });
|
let data = await load.call(this, { tmid, offset });
|
||||||
|
|
||||||
if (data && data.length) {
|
if (data && data.length) {
|
||||||
InteractionManager.runAfterInteractions(() => {
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
database.write(() => data.forEach((m) => {
|
|
||||||
try {
|
try {
|
||||||
const message = buildMessage(EJSON.fromJSONValue(m));
|
data = data.map(m => buildMessage(m));
|
||||||
message.rid = tmid;
|
const db = database.active;
|
||||||
database.create('threadMessages', message, true);
|
const threadMessagesCollection = db.collections.get('thread_messages');
|
||||||
|
const allThreadMessagesRecords = await threadMessagesCollection.query(Q.where('rid', tmid)).fetch();
|
||||||
|
let threadMessagesToCreate = data.filter(i1 => !allThreadMessagesRecords.find(i2 => i1._id === i2.id));
|
||||||
|
let threadMessagesToUpdate = allThreadMessagesRecords.filter(i1 => data.find(i2 => i1.id === i2._id));
|
||||||
|
|
||||||
|
threadMessagesToCreate = threadMessagesToCreate.map(threadMessage => threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
||||||
|
tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema);
|
||||||
|
Object.assign(tm, threadMessage);
|
||||||
|
tm.subscription.id = rid;
|
||||||
|
tm.rid = threadMessage.tmid;
|
||||||
|
delete threadMessage.tmid;
|
||||||
|
})));
|
||||||
|
|
||||||
|
threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => {
|
||||||
|
const newThreadMessage = data.find(t => t._id === threadMessage.id);
|
||||||
|
return threadMessage.prepareUpdate(protectedFunction((tm) => {
|
||||||
|
Object.assign(tm, newThreadMessage);
|
||||||
|
tm.rid = threadMessage.tmid;
|
||||||
|
delete threadMessage.tmid;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.action(async() => {
|
||||||
|
await db.batch(
|
||||||
|
...threadMessagesToCreate,
|
||||||
|
...threadMessagesToUpdate
|
||||||
|
);
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
return resolve(data);
|
return resolve(data);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return resolve([]);
|
return resolve([]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,20 +1,26 @@
|
||||||
import database from '../realm';
|
import database from '../database';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
|
||||||
export default async function readMessages(rid) {
|
export default async function readMessages(rid, lastOpen) {
|
||||||
const ls = new Date();
|
|
||||||
try {
|
try {
|
||||||
// RC 0.61.0
|
// RC 0.61.0
|
||||||
const data = await this.sdk.post('subscriptions.read', { rid });
|
const data = await this.sdk.post('subscriptions.read', { rid });
|
||||||
const [subscription] = database.objects('subscriptions').filtered('rid = $0', rid);
|
const db = database.active;
|
||||||
database.write(() => {
|
await db.action(async() => {
|
||||||
subscription.open = true;
|
try {
|
||||||
subscription.alert = false;
|
const subscription = await db.collections.get('subscriptions').find(rid);
|
||||||
subscription.unread = 0;
|
await subscription.update((s) => {
|
||||||
subscription.userMentions = 0;
|
s.open = true;
|
||||||
subscription.groupMentions = 0;
|
s.alert = false;
|
||||||
subscription.ls = ls;
|
s.unread = 0;
|
||||||
subscription.lastOpen = ls;
|
s.userMentions = 0;
|
||||||
|
s.groupMentions = 0;
|
||||||
|
s.ls = lastOpen;
|
||||||
|
s.lastOpen = lastOpen;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import database from '../realm';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
|
||||||
|
import database from '../database';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
|
||||||
const uploadQueue = {};
|
const uploadQueue = {};
|
||||||
|
@ -7,26 +9,28 @@ export function isUploadActive(path) {
|
||||||
return !!uploadQueue[path];
|
return !!uploadQueue[path];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cancelUpload(path) {
|
export async function cancelUpload(item) {
|
||||||
if (uploadQueue[path]) {
|
if (uploadQueue[item.path]) {
|
||||||
uploadQueue[path].abort();
|
uploadQueue[item.path].abort();
|
||||||
database.write(() => {
|
|
||||||
const upload = database.objects('uploads').filtered('path = $0', path);
|
|
||||||
try {
|
try {
|
||||||
database.delete(upload);
|
const db = database.active;
|
||||||
|
await db.database.action(async() => {
|
||||||
|
await item.destroyPermanently();
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
delete uploadQueue[item.path];
|
||||||
delete uploadQueue[path];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise(async(resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const { serversDB } = database.databases;
|
const serversDB = database.servers;
|
||||||
const { FileUpload_MaxFileSize, id: Site_Url } = serversDB.objectForPrimaryKey('servers', server);
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
|
const serverInfo = await serversCollection.find(server);
|
||||||
|
const { FileUpload_MaxFileSize, id: Site_Url } = serverInfo;
|
||||||
const { id, token } = user;
|
const { id, token } = user;
|
||||||
|
|
||||||
// -1 maxFileSize means there is no limit
|
// -1 maxFileSize means there is no limit
|
||||||
|
@ -41,13 +45,24 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
||||||
|
|
||||||
fileInfo.rid = rid;
|
fileInfo.rid = rid;
|
||||||
|
|
||||||
database.write(() => {
|
const db = database.active;
|
||||||
|
const uploadsCollection = db.collections.get('uploads');
|
||||||
|
let uploadRecord;
|
||||||
try {
|
try {
|
||||||
database.create('uploads', fileInfo, true);
|
uploadRecord = await uploadsCollection.find(fileInfo.path);
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
await db.action(async() => {
|
||||||
|
uploadRecord = await uploadsCollection.create((u) => {
|
||||||
|
u._raw = sanitizedRaw({ id: fileInfo.path }, uploadsCollection.schema);
|
||||||
|
Object.assign(u, fileInfo);
|
||||||
|
u.subscription.id = rid;
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return log(e);
|
return log(e);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
uploadQueue[fileInfo.path] = xhr;
|
uploadQueue[fileInfo.path] = xhr;
|
||||||
xhr.open('POST', uploadUrl);
|
xhr.open('POST', uploadUrl);
|
||||||
|
@ -69,56 +84,55 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
|
||||||
xhr.setRequestHeader('X-Auth-Token', token);
|
xhr.setRequestHeader('X-Auth-Token', token);
|
||||||
xhr.setRequestHeader('X-User-Id', id);
|
xhr.setRequestHeader('X-User-Id', id);
|
||||||
|
|
||||||
xhr.upload.onprogress = ({ total, loaded }) => {
|
xhr.upload.onprogress = async({ total, loaded }) => {
|
||||||
database.write(() => {
|
|
||||||
fileInfo.progress = Math.floor((loaded / total) * 100);
|
|
||||||
try {
|
try {
|
||||||
database.create('uploads', fileInfo, true);
|
await db.action(async() => {
|
||||||
} catch (e) {
|
await uploadRecord.update((u) => {
|
||||||
return log(e);
|
u.progress = Math.floor((loaded / total) * 100);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onload = () => {
|
xhr.onload = async() => {
|
||||||
if (xhr.status >= 200 && xhr.status < 400) { // If response is all good...
|
if (xhr.status >= 200 && xhr.status < 400) { // If response is all good...
|
||||||
database.write(() => {
|
|
||||||
const upload = database.objects('uploads').filtered('path = $0', fileInfo.path);
|
|
||||||
try {
|
try {
|
||||||
database.delete(upload);
|
await db.action(async() => {
|
||||||
|
await uploadRecord.destroyPermanently();
|
||||||
|
});
|
||||||
const response = JSON.parse(xhr.response);
|
const response = JSON.parse(xhr.response);
|
||||||
resolve(response);
|
resolve(response);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
database.write(() => {
|
|
||||||
fileInfo.error = true;
|
|
||||||
try {
|
try {
|
||||||
database.create('uploads', fileInfo, true);
|
await db.action(async() => {
|
||||||
|
await uploadRecord.update((u) => {
|
||||||
|
u.error = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
const response = JSON.parse(xhr.response);
|
const response = JSON.parse(xhr.response);
|
||||||
reject(response);
|
reject(response);
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = (error) => {
|
xhr.onerror = async(error) => {
|
||||||
database.write(() => {
|
|
||||||
fileInfo.error = true;
|
|
||||||
try {
|
try {
|
||||||
database.create('uploads', fileInfo, true);
|
await db.action(async() => {
|
||||||
reject(error);
|
await uploadRecord.update((u) => {
|
||||||
|
u.error = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
reject(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.send(formData);
|
xhr.send(formData);
|
||||||
|
|
|
@ -1,38 +1,41 @@
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
|
||||||
import messagesStatus from '../../constants/messagesStatus';
|
import messagesStatus from '../../constants/messagesStatus';
|
||||||
import buildMessage from './helpers/buildMessage';
|
import database from '../database';
|
||||||
import database from '../realm';
|
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import random from '../../utils/random';
|
import random from '../../utils/random';
|
||||||
|
|
||||||
export const getMessage = (rid, msg = '', tmid, user) => {
|
export const getMessage = async(rid, msg = '', tmid, user) => {
|
||||||
const _id = random(17);
|
const _id = random(17);
|
||||||
const { id, username } = user;
|
const { id, username } = user;
|
||||||
const message = {
|
try {
|
||||||
_id,
|
const db = database.active;
|
||||||
rid,
|
const msgCollection = db.collections.get('messages');
|
||||||
msg,
|
let message;
|
||||||
tmid,
|
await db.action(async() => {
|
||||||
ts: new Date(),
|
message = await msgCollection.create((m) => {
|
||||||
_updatedAt: new Date(),
|
m._raw = sanitizedRaw({ id: _id }, msgCollection.schema);
|
||||||
status: messagesStatus.TEMP,
|
m.subscription.id = rid;
|
||||||
u: {
|
m.msg = msg;
|
||||||
|
m.tmid = tmid;
|
||||||
|
m.ts = new Date();
|
||||||
|
m._updatedAt = new Date();
|
||||||
|
m.status = messagesStatus.TEMP;
|
||||||
|
m.u = {
|
||||||
_id: id || '1',
|
_id: id || '1',
|
||||||
username
|
username
|
||||||
}
|
|
||||||
};
|
};
|
||||||
try {
|
|
||||||
database.write(() => {
|
|
||||||
database.create('messages', message, true);
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
return message;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('getMessage', error);
|
console.warn('getMessage', error);
|
||||||
}
|
}
|
||||||
return message;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function sendMessageCall(message) {
|
export async function sendMessageCall(message) {
|
||||||
const {
|
const {
|
||||||
_id, rid, msg, tmid
|
id: _id, subscription: { id: rid }, msg, tmid
|
||||||
} = message;
|
} = message;
|
||||||
// RC 0.60.0
|
// RC 0.60.0
|
||||||
const data = await this.sdk.post('chat.sendMessage', {
|
const data = await this.sdk.post('chat.sendMessage', {
|
||||||
|
@ -45,24 +48,36 @@ export async function sendMessageCall(message) {
|
||||||
|
|
||||||
export default async function(rid, msg, tmid, user) {
|
export default async function(rid, msg, tmid, user) {
|
||||||
try {
|
try {
|
||||||
const message = getMessage(rid, msg, tmid, user);
|
const db = database.active;
|
||||||
const [room] = database.objects('subscriptions').filtered('rid == $0', rid);
|
const subsCollections = db.collections.get('subscriptions');
|
||||||
|
const message = await getMessage(rid, msg, tmid, user);
|
||||||
if (room) {
|
if (!message) {
|
||||||
database.write(() => {
|
return;
|
||||||
room.draftMessage = null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ret = await sendMessageCall.call(this, message);
|
const room = await subsCollections.find(rid);
|
||||||
database.write(() => {
|
await db.action(async() => {
|
||||||
database.create('messages', buildMessage({ ...message, ...ret }), true);
|
await room.update((r) => {
|
||||||
|
r.draftMessage = null;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
database.write(() => {
|
// Do nothing
|
||||||
message.status = messagesStatus.ERROR;
|
}
|
||||||
database.create('messages', message, true);
|
|
||||||
|
try {
|
||||||
|
await sendMessageCall.call(this, message);
|
||||||
|
await db.action(async() => {
|
||||||
|
await message.update((m) => {
|
||||||
|
m.status = messagesStatus.SENT;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
await db.action(async() => {
|
||||||
|
await message.update((m) => {
|
||||||
|
m.status = messagesStatus.ERROR;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
import EJSON from 'ejson';
|
import EJSON from 'ejson';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
import { InteractionManager } from 'react-native';
|
||||||
|
|
||||||
import log from '../../../utils/log';
|
import log from '../../../utils/log';
|
||||||
import protectedFunction from '../helpers/protectedFunction';
|
import protectedFunction from '../helpers/protectedFunction';
|
||||||
import buildMessage from '../helpers/buildMessage';
|
import buildMessage from '../helpers/buildMessage';
|
||||||
import database from '../../realm';
|
import database from '../../database';
|
||||||
import debounce from '../../../utils/debounce';
|
import reduxStore from '../../createStore';
|
||||||
|
import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actions/usersTyping';
|
||||||
|
|
||||||
const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch(() => console.log('unsubscribeRoom')));
|
const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch(() => console.log('unsubscribeRoom')));
|
||||||
const removeListener = listener => listener.stop();
|
const removeListener = listener => listener.stop();
|
||||||
|
|
||||||
export default function subscribeRoom({ rid }) {
|
export default function subscribeRoom({ rid }) {
|
||||||
|
console.log(`[RCRN] Subscribed to room ${ rid }`);
|
||||||
let promises;
|
let promises;
|
||||||
let connectedListener;
|
let connectedListener;
|
||||||
let disconnectedListener;
|
let disconnectedListener;
|
||||||
|
@ -18,52 +22,7 @@ export default function subscribeRoom({ rid }) {
|
||||||
const typingTimeouts = {};
|
const typingTimeouts = {};
|
||||||
|
|
||||||
const handleConnection = () => {
|
const handleConnection = () => {
|
||||||
this.loadMissedMessages({ rid });
|
this.loadMissedMessages({ rid }).catch(e => console.log(e));
|
||||||
};
|
|
||||||
|
|
||||||
const getUserTyping = username => (
|
|
||||||
database
|
|
||||||
.memoryDatabase.objects('usersTyping')
|
|
||||||
.filtered('rid = $0 AND username = $1', rid, username)
|
|
||||||
);
|
|
||||||
|
|
||||||
const removeUserTyping = (username) => {
|
|
||||||
const userTyping = getUserTyping(username);
|
|
||||||
try {
|
|
||||||
database.memoryDatabase.write(() => {
|
|
||||||
database.memoryDatabase.delete(userTyping);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typingTimeouts[username]) {
|
|
||||||
clearTimeout(typingTimeouts[username]);
|
|
||||||
typingTimeouts[username] = null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const addUserTyping = (username) => {
|
|
||||||
const userTyping = getUserTyping(username);
|
|
||||||
// prevent duplicated
|
|
||||||
if (userTyping.length === 0) {
|
|
||||||
try {
|
|
||||||
database.memoryDatabase.write(() => {
|
|
||||||
database.memoryDatabase.create('usersTyping', { rid, username });
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typingTimeouts[username]) {
|
|
||||||
clearTimeout(typingTimeouts[username]);
|
|
||||||
typingTimeouts[username] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
typingTimeouts[username] = setTimeout(() => {
|
|
||||||
removeUserTyping(username);
|
|
||||||
}, 10000);
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNotifyRoomReceived = protectedFunction((ddpMessage) => {
|
const handleNotifyRoomReceived = protectedFunction((ddpMessage) => {
|
||||||
|
@ -74,59 +33,147 @@ export default function subscribeRoom({ rid }) {
|
||||||
if (ev === 'typing') {
|
if (ev === 'typing') {
|
||||||
const [username, typing] = ddpMessage.fields.args;
|
const [username, typing] = ddpMessage.fields.args;
|
||||||
if (typing) {
|
if (typing) {
|
||||||
addUserTyping(username);
|
reduxStore.dispatch(addUserTyping(username));
|
||||||
} else {
|
} else {
|
||||||
removeUserTyping(username);
|
reduxStore.dispatch(removeUserTyping(username));
|
||||||
}
|
}
|
||||||
} else if (ev === 'deleteMessage') {
|
} else if (ev === 'deleteMessage') {
|
||||||
database.write(() => {
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
if (ddpMessage && ddpMessage.fields && ddpMessage.fields.args.length > 0) {
|
if (ddpMessage && ddpMessage.fields && ddpMessage.fields.args.length > 0) {
|
||||||
|
try {
|
||||||
const { _id } = ddpMessage.fields.args[0];
|
const { _id } = ddpMessage.fields.args[0];
|
||||||
const message = database.objects('messages').filtered('_id = $0', _id);
|
const db = database.active;
|
||||||
database.delete(message);
|
const msgCollection = db.collections.get('messages');
|
||||||
const thread = database.objects('threads').filtered('_id = $0', _id);
|
const threadsCollection = db.collections.get('threads');
|
||||||
database.delete(thread);
|
const threadMessagesCollection = db.collections.get('thread_messages');
|
||||||
const threadMessage = database.objects('threadMessages').filtered('_id = $0', _id);
|
let deleteMessage;
|
||||||
database.delete(threadMessage);
|
let deleteThread;
|
||||||
const cleanTmids = database.objects('messages').filtered('tmid = $0', _id).snapshot();
|
let deleteThreadMessage;
|
||||||
if (cleanTmids && cleanTmids.length) {
|
|
||||||
cleanTmids.forEach((m) => {
|
|
||||||
m.tmid = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const read = debounce(() => {
|
// Delete message
|
||||||
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
try {
|
||||||
if (room && room._id) {
|
const m = await msgCollection.find(_id);
|
||||||
this.readMessages(rid);
|
deleteMessage = m.prepareDestroyPermanently();
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
}
|
}
|
||||||
}, 300);
|
|
||||||
|
// Delete thread
|
||||||
|
try {
|
||||||
|
const m = await threadsCollection.find(_id);
|
||||||
|
deleteThread = m.prepareDestroyPermanently();
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete thread message
|
||||||
|
try {
|
||||||
|
const m = await threadMessagesCollection.find(_id);
|
||||||
|
deleteThreadMessage = m.prepareDestroyPermanently();
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
await db.action(async() => {
|
||||||
|
await db.batch(
|
||||||
|
deleteMessage, deleteThread, deleteThreadMessage
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const handleMessageReceived = protectedFunction((ddpMessage) => {
|
const handleMessageReceived = protectedFunction((ddpMessage) => {
|
||||||
const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0]));
|
const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0]));
|
||||||
if (rid !== message.rid) {
|
if (rid !== message.rid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
requestAnimationFrame(() => {
|
InteractionManager.runAfterInteractions(async() => {
|
||||||
try {
|
const db = database.active;
|
||||||
database.write(() => {
|
const batch = [];
|
||||||
database.create('messages', message, true);
|
const subCollection = db.collections.get('subscriptions');
|
||||||
// if it's a thread "header"
|
const msgCollection = db.collections.get('messages');
|
||||||
if (message.tlm) {
|
const threadsCollection = db.collections.get('threads');
|
||||||
database.create('threads', message, true);
|
const threadMessagesCollection = db.collections.get('thread_messages');
|
||||||
} else if (message.tmid) {
|
|
||||||
message.rid = message.tmid;
|
|
||||||
database.create('threadMessages', message, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
read();
|
// Create or update message
|
||||||
|
try {
|
||||||
|
const messageRecord = await msgCollection.find(message._id);
|
||||||
|
batch.push(
|
||||||
|
messageRecord.prepareUpdate((m) => {
|
||||||
|
Object.assign(m, message);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
batch.push(
|
||||||
|
msgCollection.prepareCreate(protectedFunction((m) => {
|
||||||
|
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
||||||
|
m.subscription.id = rid;
|
||||||
|
Object.assign(m, message);
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or update thread
|
||||||
|
if (message.tlm) {
|
||||||
|
try {
|
||||||
|
const threadRecord = await threadsCollection.find(message._id);
|
||||||
|
batch.push(
|
||||||
|
threadRecord.prepareUpdate((t) => {
|
||||||
|
Object.assign(t, message);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
batch.push(
|
||||||
|
threadsCollection.prepareCreate(protectedFunction((t) => {
|
||||||
|
t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
|
||||||
|
t.subscription.id = rid;
|
||||||
|
Object.assign(t, message);
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or update thread message
|
||||||
|
if (message.tmid) {
|
||||||
|
try {
|
||||||
|
const threadMessageRecord = await threadMessagesCollection.find(message._id);
|
||||||
|
batch.push(
|
||||||
|
threadMessageRecord.prepareUpdate((tm) => {
|
||||||
|
Object.assign(tm, message);
|
||||||
|
tm.rid = message.tmid;
|
||||||
|
delete tm.tmid;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
batch.push(
|
||||||
|
threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
||||||
|
tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
|
||||||
|
Object.assign(tm, message);
|
||||||
|
tm.subscription.id = rid;
|
||||||
|
tm.rid = message.tmid;
|
||||||
|
delete tm.tmid;
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await subCollection.find(rid);
|
||||||
|
this.readMessages(rid);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('handleMessageReceived', e);
|
console.log('Subscription not found. We probably subscribed to a not joined channel. No need to mark as read.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.action(async() => {
|
||||||
|
await db.batch(...batch);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -158,10 +205,7 @@ export default function subscribeRoom({ rid }) {
|
||||||
typingTimeouts[key] = null;
|
typingTimeouts[key] = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
database.memoryDatabase.write(() => {
|
reduxStore.dispatch(clearUserTyping());
|
||||||
const usersTyping = database.memoryDatabase.objects('usersTyping').filtered('rid == $0', rid);
|
|
||||||
database.memoryDatabase.delete(usersTyping);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
connectedListener = this.sdk.onStreamData('connected', handleConnection);
|
connectedListener = this.sdk.onStreamData('connected', handleConnection);
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import database from '../../realm';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
|
||||||
|
import database from '../../database';
|
||||||
import { merge } from '../helpers/mergeSubscriptionsRooms';
|
import { merge } from '../helpers/mergeSubscriptionsRooms';
|
||||||
import protectedFunction from '../helpers/protectedFunction';
|
import protectedFunction from '../helpers/protectedFunction';
|
||||||
import messagesStatus from '../../../constants/messagesStatus';
|
import messagesStatus from '../../../constants/messagesStatus';
|
||||||
|
@ -15,12 +17,112 @@ let disconnectedListener;
|
||||||
let streamListener;
|
let streamListener;
|
||||||
let subServer;
|
let subServer;
|
||||||
|
|
||||||
|
// TODO: batch execution
|
||||||
|
const createOrUpdateSubscription = async(subscription, room) => {
|
||||||
|
try {
|
||||||
|
const db = database.active;
|
||||||
|
const subCollection = db.collections.get('subscriptions');
|
||||||
|
const roomsCollection = db.collections.get('rooms');
|
||||||
|
|
||||||
|
if (!subscription) {
|
||||||
|
try {
|
||||||
|
const s = await subCollection.find(room._id);
|
||||||
|
// We have to create a plain obj so we can manipulate it on `merge`
|
||||||
|
// Can we do it in a better way?
|
||||||
|
subscription = {
|
||||||
|
_id: s._id,
|
||||||
|
f: s.f,
|
||||||
|
t: s.t,
|
||||||
|
ts: s.ts,
|
||||||
|
ls: s.ls,
|
||||||
|
name: s.name,
|
||||||
|
fname: s.fname,
|
||||||
|
rid: s.rid,
|
||||||
|
open: s.open,
|
||||||
|
alert: s.alert,
|
||||||
|
unread: s.unread,
|
||||||
|
userMentions: s.userMentions,
|
||||||
|
roomUpdatedAt: s.roomUpdatedAt,
|
||||||
|
ro: s.ro,
|
||||||
|
lastOpen: s.lastOpen,
|
||||||
|
description: s.description,
|
||||||
|
announcement: s.announcement,
|
||||||
|
topic: s.topic,
|
||||||
|
blocked: s.blocked,
|
||||||
|
blocker: s.blocker,
|
||||||
|
reactWhenReadOnly: s.reactWhenReadOnly,
|
||||||
|
archived: s.archived,
|
||||||
|
joinCodeRequired: s.joinCodeRequired,
|
||||||
|
muted: s.muted,
|
||||||
|
broadcast: s.broadcast,
|
||||||
|
prid: s.prid,
|
||||||
|
draftMessage: s.draftMessage,
|
||||||
|
lastThreadSync: s.lastThreadSync,
|
||||||
|
autoTranslate: s.autoTranslate,
|
||||||
|
autoTranslateLanguage: s.autoTranslateLanguage,
|
||||||
|
lastMessage: s.lastMessage
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
await db.action(async() => {
|
||||||
|
await roomsCollection.create((r) => {
|
||||||
|
r._raw = sanitizedRaw({ id: room._id }, roomsCollection.schema);
|
||||||
|
Object.assign(r, room);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!room && subscription) {
|
||||||
|
try {
|
||||||
|
const r = await roomsCollection.find(subscription.rid);
|
||||||
|
// We have to create a plain obj so we can manipulate it on `merge`
|
||||||
|
// Can we do it in a better way?
|
||||||
|
room = {
|
||||||
|
customFields: r.customFields,
|
||||||
|
broadcast: r.broadcast,
|
||||||
|
encrypted: r.encrypted,
|
||||||
|
ro: r.ro
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tmp = merge(subscription, room);
|
||||||
|
await db.action(async() => {
|
||||||
|
try {
|
||||||
|
const sub = await subCollection.find(tmp.rid);
|
||||||
|
await sub.update((s) => {
|
||||||
|
Object.assign(s, tmp);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await subCollection.create((s) => {
|
||||||
|
s._raw = sanitizedRaw({ id: tmp.rid }, subCollection.schema);
|
||||||
|
Object.assign(s, tmp);
|
||||||
|
if (s.roomUpdatedAt) {
|
||||||
|
s.roomUpdatedAt = new Date();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default function subscribeRooms() {
|
export default function subscribeRooms() {
|
||||||
const handleConnection = () => {
|
const handleConnection = () => {
|
||||||
store.dispatch(roomsRequest());
|
store.dispatch(roomsRequest());
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStreamMessageReceived = protectedFunction((ddpMessage) => {
|
const handleStreamMessageReceived = protectedFunction(async(ddpMessage) => {
|
||||||
|
const db = database.active;
|
||||||
|
|
||||||
// check if the server from variable is the same as the js sdk client
|
// check if the server from variable is the same as the js sdk client
|
||||||
if (this.sdk && this.sdk.client && this.sdk.client.host !== subServer) {
|
if (this.sdk && this.sdk.client && this.sdk.client.host !== subServer) {
|
||||||
return;
|
return;
|
||||||
|
@ -32,52 +134,33 @@ export default function subscribeRooms() {
|
||||||
const [, ev] = ddpMessage.fields.eventName.split('/');
|
const [, ev] = ddpMessage.fields.eventName.split('/');
|
||||||
if (/subscriptions/.test(ev)) {
|
if (/subscriptions/.test(ev)) {
|
||||||
if (type === 'removed') {
|
if (type === 'removed') {
|
||||||
let messages = [];
|
|
||||||
const [subscription] = database.objects('subscriptions').filtered('_id == $0', data._id);
|
|
||||||
|
|
||||||
if (subscription) {
|
|
||||||
messages = database.objects('messages').filtered('rid == $0', subscription.rid);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
database.write(() => {
|
const subCollection = db.collections.get('subscriptions');
|
||||||
database.delete(messages);
|
const sub = await subCollection.find(data.rid);
|
||||||
database.delete(subscription);
|
const messages = await sub.messages.fetch();
|
||||||
|
const threads = await sub.threads.fetch();
|
||||||
|
const threadMessages = await sub.threadMessages.fetch();
|
||||||
|
const messagesToDelete = messages.map(m => m.prepareDestroyPermanently());
|
||||||
|
const threadsToDelete = threads.map(m => m.prepareDestroyPermanently());
|
||||||
|
const threadMessagesToDelete = threadMessages.map(m => m.prepareDestroyPermanently());
|
||||||
|
await db.action(async() => {
|
||||||
|
await db.batch(
|
||||||
|
sub.prepareDestroyPermanently(),
|
||||||
|
...messagesToDelete,
|
||||||
|
...threadsToDelete,
|
||||||
|
...threadMessagesToDelete,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const rooms = database.objects('rooms').filtered('_id == $0', data.rid);
|
await createOrUpdateSubscription(data);
|
||||||
const tpm = merge(data, rooms[0]);
|
|
||||||
try {
|
|
||||||
database.write(() => {
|
|
||||||
database.create('subscriptions', tpm, true);
|
|
||||||
database.delete(rooms);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (/rooms/.test(ev)) {
|
if (/rooms/.test(ev)) {
|
||||||
if (type === 'updated') {
|
if (type === 'updated' || type === 'inserted') {
|
||||||
const [sub] = database.objects('subscriptions').filtered('rid == $0', data._id);
|
await createOrUpdateSubscription(null, data);
|
||||||
try {
|
|
||||||
database.write(() => {
|
|
||||||
const tmp = merge(sub, data);
|
|
||||||
database.create('subscriptions', tmp, true);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
} else if (type === 'inserted') {
|
|
||||||
try {
|
|
||||||
database.write(() => {
|
|
||||||
database.create('rooms', data, true);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (/message/.test(ev)) {
|
if (/message/.test(ev)) {
|
||||||
|
@ -95,15 +178,18 @@ export default function subscribeRooms() {
|
||||||
username: 'rocket.cat'
|
username: 'rocket.cat'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
requestAnimationFrame(() => {
|
|
||||||
try {
|
try {
|
||||||
database.write(() => {
|
const msgCollection = db.collections.get('messages');
|
||||||
database.create('messages', message, true);
|
await db.action(async() => {
|
||||||
|
await msgCollection.create(protectedFunction((m) => {
|
||||||
|
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
||||||
|
m.subscription.id = args.rid;
|
||||||
|
Object.assign(m, message);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (/notification/.test(ev)) {
|
if (/notification/.test(ev)) {
|
||||||
const [notification] = ddpMessage.fields.args;
|
const [notification] = ddpMessage.fields.args;
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
|
import buildMessage from './helpers/buildMessage';
|
||||||
|
import log from '../../utils/log';
|
||||||
|
import database from '../database';
|
||||||
|
import protectedFunction from './helpers/protectedFunction';
|
||||||
|
|
||||||
|
export default function updateMessages({ rid, update, remove }) {
|
||||||
|
try {
|
||||||
|
if (!((update && update.length) || (remove && remove.length))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const db = database.active;
|
||||||
|
return db.action(async() => {
|
||||||
|
const subCollection = db.collections.get('subscriptions');
|
||||||
|
let sub;
|
||||||
|
try {
|
||||||
|
sub = await subCollection.find(rid);
|
||||||
|
} catch (error) {
|
||||||
|
sub = { id: rid };
|
||||||
|
console.log('updateMessages: subscription not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const messagesIds = update.map(m => m._id);
|
||||||
|
const msgCollection = db.collections.get('messages');
|
||||||
|
const threadCollection = db.collections.get('threads');
|
||||||
|
const threadMessagesCollection = db.collections.get('thread_messages');
|
||||||
|
const allMessagesRecords = await msgCollection
|
||||||
|
.query(Q.where('rid', rid), Q.where('id', Q.oneOf(messagesIds)))
|
||||||
|
.fetch();
|
||||||
|
const allThreadsRecords = await threadCollection
|
||||||
|
.query(Q.where('rid', rid), Q.where('id', Q.oneOf(messagesIds)))
|
||||||
|
.fetch();
|
||||||
|
const allThreadMessagesRecords = await threadMessagesCollection
|
||||||
|
.query(Q.where('subscription_id', rid), Q.where('id', Q.oneOf(messagesIds)))
|
||||||
|
.fetch();
|
||||||
|
|
||||||
|
update = update.map(m => buildMessage(m));
|
||||||
|
|
||||||
|
// filter messages
|
||||||
|
let msgsToCreate = update.filter(i1 => !allMessagesRecords.find(i2 => i1._id === i2.id));
|
||||||
|
let msgsToUpdate = allMessagesRecords.filter(i1 => update.find(i2 => i1.id === i2._id));
|
||||||
|
|
||||||
|
// filter threads
|
||||||
|
const allThreads = update.filter(m => m.tlm);
|
||||||
|
let threadsToCreate = allThreads.filter(i1 => !allThreadsRecords.find(i2 => i1._id === i2.id));
|
||||||
|
let threadsToUpdate = allThreadsRecords.filter(i1 => allThreads.find(i2 => i1.id === i2._id));
|
||||||
|
|
||||||
|
// filter thread messages
|
||||||
|
const allThreadMessages = update.filter(m => m.tmid);
|
||||||
|
let threadMessagesToCreate = allThreadMessages.filter(i1 => !allThreadMessagesRecords.find(i2 => i1._id === i2.id));
|
||||||
|
let threadMessagesToUpdate = allThreadMessagesRecords.filter(i1 => allThreadMessages.find(i2 => i1.id === i2._id));
|
||||||
|
|
||||||
|
// Create
|
||||||
|
msgsToCreate = msgsToCreate.map(message => msgCollection.prepareCreate(protectedFunction((m) => {
|
||||||
|
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
||||||
|
m.subscription.id = sub.id;
|
||||||
|
Object.assign(m, message);
|
||||||
|
})));
|
||||||
|
threadsToCreate = threadsToCreate.map(thread => threadCollection.prepareCreate(protectedFunction((t) => {
|
||||||
|
t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
|
||||||
|
t.subscription.id = sub.id;
|
||||||
|
Object.assign(t, thread);
|
||||||
|
})));
|
||||||
|
threadMessagesToCreate = threadMessagesToCreate.map(threadMessage => threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
||||||
|
tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema);
|
||||||
|
Object.assign(tm, threadMessage);
|
||||||
|
tm.subscription.id = sub.id;
|
||||||
|
tm.rid = threadMessage.tmid;
|
||||||
|
delete threadMessage.tmid;
|
||||||
|
})));
|
||||||
|
|
||||||
|
// Update
|
||||||
|
msgsToUpdate = msgsToUpdate.map((message) => {
|
||||||
|
const newMessage = update.find(m => m._id === message.id);
|
||||||
|
return message.prepareUpdate(protectedFunction((m) => {
|
||||||
|
Object.assign(m, newMessage);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
threadsToUpdate = threadsToUpdate.map((thread) => {
|
||||||
|
const newThread = allThreads.find(t => t._id === thread.id);
|
||||||
|
return thread.prepareUpdate(protectedFunction((t) => {
|
||||||
|
Object.assign(t, newThread);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => {
|
||||||
|
const newThreadMessage = allThreadMessages.find(t => t._id === threadMessage.id);
|
||||||
|
return threadMessage.prepareUpdate(protectedFunction((tm) => {
|
||||||
|
Object.assign(tm, newThreadMessage);
|
||||||
|
tm.rid = threadMessage.tmid;
|
||||||
|
delete threadMessage.tmid;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
let msgsToDelete = [];
|
||||||
|
let threadsToDelete = [];
|
||||||
|
let threadMessagesToDelete = [];
|
||||||
|
if (remove && remove.length) {
|
||||||
|
msgsToDelete = allMessagesRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
|
||||||
|
msgsToDelete = msgsToDelete.map(m => m.prepareDestroyPermanently());
|
||||||
|
threadsToDelete = allThreadsRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
|
||||||
|
threadsToDelete = threadsToDelete.map(t => t.prepareDestroyPermanently());
|
||||||
|
threadMessagesToDelete = allThreadMessagesRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
|
||||||
|
threadMessagesToDelete = threadMessagesToDelete.map(tm => tm.prepareDestroyPermanently());
|
||||||
|
}
|
||||||
|
|
||||||
|
const allRecords = [
|
||||||
|
...msgsToCreate,
|
||||||
|
...msgsToUpdate,
|
||||||
|
...msgsToDelete,
|
||||||
|
...threadsToCreate,
|
||||||
|
...threadsToUpdate,
|
||||||
|
...threadsToDelete,
|
||||||
|
...threadMessagesToCreate,
|
||||||
|
...threadMessagesToUpdate,
|
||||||
|
...threadMessagesToDelete
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.batch(...allRecords);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
return allRecords.length;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
}
|
526
app/lib/realm.js
526
app/lib/realm.js
|
@ -1,526 +0,0 @@
|
||||||
import Realm from 'realm';
|
|
||||||
import RNRealmPath from 'react-native-realm-path';
|
|
||||||
|
|
||||||
// import { AsyncStorage } from 'react-native';
|
|
||||||
// Realm.clearTestState();
|
|
||||||
// AsyncStorage.clear();
|
|
||||||
|
|
||||||
const userSchema = {
|
|
||||||
name: 'user',
|
|
||||||
primaryKey: 'id',
|
|
||||||
properties: {
|
|
||||||
id: 'string',
|
|
||||||
token: { type: 'string', optional: true },
|
|
||||||
username: { type: 'string', optional: true },
|
|
||||||
name: { type: 'string', optional: true },
|
|
||||||
language: { type: 'string', optional: true },
|
|
||||||
status: { type: 'string', optional: true },
|
|
||||||
roles: { type: 'string[]', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const serversSchema = {
|
|
||||||
name: 'servers',
|
|
||||||
primaryKey: 'id',
|
|
||||||
properties: {
|
|
||||||
id: 'string',
|
|
||||||
name: { type: 'string', optional: true },
|
|
||||||
iconURL: { type: 'string', optional: true },
|
|
||||||
useRealName: { type: 'bool', optional: true },
|
|
||||||
FileUpload_MediaTypeWhiteList: { type: 'string', optional: true },
|
|
||||||
FileUpload_MaxFileSize: { type: 'int', optional: true },
|
|
||||||
roomsUpdatedAt: { type: 'date', optional: true },
|
|
||||||
version: 'string?'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const settingsSchema = {
|
|
||||||
name: 'settings',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
valueAsString: { type: 'string', optional: true },
|
|
||||||
valueAsBoolean: { type: 'bool', optional: true },
|
|
||||||
valueAsNumber: { type: 'int', optional: true },
|
|
||||||
_updatedAt: { type: 'date', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const permissionsSchema = {
|
|
||||||
name: 'permissions',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
roles: 'string[]',
|
|
||||||
_updatedAt: { type: 'date', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const roomsSchema = {
|
|
||||||
name: 'rooms',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
name: 'string?',
|
|
||||||
broadcast: { type: 'bool', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const subscriptionSchema = {
|
|
||||||
name: 'subscriptions',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
f: { type: 'bool', optional: true },
|
|
||||||
t: 'string',
|
|
||||||
ts: { type: 'date', optional: true },
|
|
||||||
ls: { type: 'date', optional: true },
|
|
||||||
name: { type: 'string', indexed: true },
|
|
||||||
fname: { type: 'string', optional: true },
|
|
||||||
rid: { type: 'string', indexed: true },
|
|
||||||
open: { type: 'bool', optional: true },
|
|
||||||
alert: { type: 'bool', optional: true },
|
|
||||||
roles: 'string[]',
|
|
||||||
unread: { type: 'int', optional: true },
|
|
||||||
userMentions: { type: 'int', optional: true },
|
|
||||||
roomUpdatedAt: { type: 'date', optional: true },
|
|
||||||
ro: { type: 'bool', optional: true },
|
|
||||||
lastOpen: { type: 'date', optional: true },
|
|
||||||
lastMessage: { type: 'messages', optional: true },
|
|
||||||
description: { type: 'string', optional: true },
|
|
||||||
announcement: { type: 'string', optional: true },
|
|
||||||
topic: { type: 'string', optional: true },
|
|
||||||
blocked: { type: 'bool', optional: true },
|
|
||||||
blocker: { type: 'bool', optional: true },
|
|
||||||
reactWhenReadOnly: { type: 'bool', optional: true },
|
|
||||||
archived: { type: 'bool', optional: true },
|
|
||||||
joinCodeRequired: { type: 'bool', optional: true },
|
|
||||||
muted: 'string[]',
|
|
||||||
broadcast: { type: 'bool', optional: true },
|
|
||||||
prid: { type: 'string', optional: true },
|
|
||||||
draftMessage: { type: 'string', optional: true },
|
|
||||||
lastThreadSync: 'date?',
|
|
||||||
autoTranslate: 'bool?',
|
|
||||||
autoTranslateLanguage: 'string?',
|
|
||||||
// Notifications
|
|
||||||
emailNotifications: { type: 'string', default: 'default' },
|
|
||||||
disableNotifications: { type: 'bool', default: false },
|
|
||||||
muteGroupMentions: { type: 'bool', default: false },
|
|
||||||
hideUnreadStatus: { type: 'bool', default: false },
|
|
||||||
audioNotifications: { type: 'string', default: 'default' },
|
|
||||||
desktopNotifications: { type: 'string', default: 'default' },
|
|
||||||
audioNotificationValue: { type: 'string', default: '0 Default' },
|
|
||||||
desktopNotificationDuration: { type: 'int', default: 0 },
|
|
||||||
mobilePushNotifications: { type: 'string', default: 'default' }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const usersSchema = {
|
|
||||||
name: 'users',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
username: 'string',
|
|
||||||
name: { type: 'string', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const attachmentFields = {
|
|
||||||
name: 'attachmentFields',
|
|
||||||
properties: {
|
|
||||||
title: { type: 'string', optional: true },
|
|
||||||
value: { type: 'string', optional: true },
|
|
||||||
short: { type: 'bool', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const attachment = {
|
|
||||||
name: 'attachment',
|
|
||||||
properties: {
|
|
||||||
description: { type: 'string', optional: true },
|
|
||||||
image_size: { type: 'int', optional: true },
|
|
||||||
image_type: { type: 'string', optional: true },
|
|
||||||
image_url: { type: 'string', optional: true },
|
|
||||||
audio_size: { type: 'int', optional: true },
|
|
||||||
audio_type: { type: 'string', optional: true },
|
|
||||||
audio_url: { type: 'string', optional: true },
|
|
||||||
video_size: { type: 'int', optional: true },
|
|
||||||
video_type: { type: 'string', optional: true },
|
|
||||||
video_url: { type: 'string', optional: true },
|
|
||||||
title: { type: 'string', optional: true },
|
|
||||||
title_link: { type: 'string', optional: true },
|
|
||||||
// title_link_download: { type: 'bool', optional: true },
|
|
||||||
type: { type: 'string', optional: true },
|
|
||||||
author_icon: { type: 'string', optional: true },
|
|
||||||
author_name: { type: 'string', optional: true },
|
|
||||||
author_link: { type: 'string', optional: true },
|
|
||||||
text: { type: 'string', optional: true },
|
|
||||||
color: { type: 'string', optional: true },
|
|
||||||
ts: { type: 'date', optional: true },
|
|
||||||
attachments: { type: 'list', objectType: 'attachment' },
|
|
||||||
fields: {
|
|
||||||
type: 'list', objectType: 'attachmentFields', default: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const url = {
|
|
||||||
name: 'url',
|
|
||||||
primaryKey: 'url',
|
|
||||||
properties: {
|
|
||||||
// _id: { type: 'int', optional: true },
|
|
||||||
url: { type: 'string', optional: true },
|
|
||||||
title: { type: 'string', optional: true },
|
|
||||||
description: { type: 'string', optional: true },
|
|
||||||
image: { type: 'string', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const messagesReactionsSchema = {
|
|
||||||
name: 'messagesReactions',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
emoji: 'string',
|
|
||||||
usernames: 'string[]'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const messagesTranslationsSchema = {
|
|
||||||
name: 'messagesTranslations',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
language: 'string',
|
|
||||||
value: 'string'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const messagesEditedBySchema = {
|
|
||||||
name: 'messagesEditedBy',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: { type: 'string', optional: true },
|
|
||||||
username: { type: 'string', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const messagesSchema = {
|
|
||||||
name: 'messages',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
msg: { type: 'string', optional: true },
|
|
||||||
t: { type: 'string', optional: true },
|
|
||||||
rid: { type: 'string', indexed: true },
|
|
||||||
ts: 'date',
|
|
||||||
u: 'users',
|
|
||||||
alias: { type: 'string', optional: true },
|
|
||||||
parseUrls: { type: 'bool', optional: true },
|
|
||||||
groupable: { type: 'bool', optional: true },
|
|
||||||
avatar: { type: 'string', optional: true },
|
|
||||||
attachments: { type: 'list', objectType: 'attachment' },
|
|
||||||
urls: { type: 'list', objectType: 'url', default: [] },
|
|
||||||
_updatedAt: { type: 'date', optional: true },
|
|
||||||
status: { type: 'int', optional: true },
|
|
||||||
pinned: { type: 'bool', optional: true },
|
|
||||||
starred: { type: 'bool', optional: true },
|
|
||||||
editedBy: 'messagesEditedBy',
|
|
||||||
reactions: { type: 'list', objectType: 'messagesReactions' },
|
|
||||||
role: { type: 'string', optional: true },
|
|
||||||
drid: { type: 'string', optional: true },
|
|
||||||
dcount: { type: 'int', optional: true },
|
|
||||||
dlm: { type: 'date', optional: true },
|
|
||||||
tmid: { type: 'string', optional: true },
|
|
||||||
tcount: { type: 'int', optional: true },
|
|
||||||
tlm: { type: 'date', optional: true },
|
|
||||||
replies: 'string[]',
|
|
||||||
mentions: { type: 'list', objectType: 'users' },
|
|
||||||
channels: { type: 'list', objectType: 'rooms' },
|
|
||||||
unread: { type: 'bool', optional: true },
|
|
||||||
autoTranslate: { type: 'bool', default: false },
|
|
||||||
translations: { type: 'list', objectType: 'messagesTranslations' }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const threadsSchema = {
|
|
||||||
name: 'threads',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
msg: { type: 'string', optional: true },
|
|
||||||
t: { type: 'string', optional: true },
|
|
||||||
rid: { type: 'string', indexed: true },
|
|
||||||
ts: 'date',
|
|
||||||
u: 'users',
|
|
||||||
alias: { type: 'string', optional: true },
|
|
||||||
parseUrls: { type: 'bool', optional: true },
|
|
||||||
groupable: { type: 'bool', optional: true },
|
|
||||||
avatar: { type: 'string', optional: true },
|
|
||||||
attachments: { type: 'list', objectType: 'attachment' },
|
|
||||||
urls: { type: 'list', objectType: 'url', default: [] },
|
|
||||||
_updatedAt: { type: 'date', optional: true },
|
|
||||||
status: { type: 'int', optional: true },
|
|
||||||
pinned: { type: 'bool', optional: true },
|
|
||||||
starred: { type: 'bool', optional: true },
|
|
||||||
editedBy: 'messagesEditedBy',
|
|
||||||
reactions: { type: 'list', objectType: 'messagesReactions' },
|
|
||||||
role: { type: 'string', optional: true },
|
|
||||||
drid: { type: 'string', optional: true },
|
|
||||||
dcount: { type: 'int', optional: true },
|
|
||||||
dlm: { type: 'date', optional: true },
|
|
||||||
tmid: { type: 'string', optional: true },
|
|
||||||
tcount: { type: 'int', optional: true },
|
|
||||||
tlm: { type: 'date', optional: true },
|
|
||||||
replies: 'string[]',
|
|
||||||
mentions: { type: 'list', objectType: 'users' },
|
|
||||||
channels: { type: 'list', objectType: 'rooms' },
|
|
||||||
unread: { type: 'bool', optional: true },
|
|
||||||
autoTranslate: { type: 'bool', default: false },
|
|
||||||
translations: { type: 'list', objectType: 'messagesTranslations' },
|
|
||||||
draftMessage: 'string?'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const threadMessagesSchema = {
|
|
||||||
name: 'threadMessages',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
msg: { type: 'string', optional: true },
|
|
||||||
t: { type: 'string', optional: true },
|
|
||||||
rid: { type: 'string', indexed: true },
|
|
||||||
ts: 'date',
|
|
||||||
u: 'users',
|
|
||||||
alias: { type: 'string', optional: true },
|
|
||||||
parseUrls: { type: 'bool', optional: true },
|
|
||||||
groupable: { type: 'bool', optional: true },
|
|
||||||
avatar: { type: 'string', optional: true },
|
|
||||||
attachments: { type: 'list', objectType: 'attachment' },
|
|
||||||
urls: { type: 'list', objectType: 'url', default: [] },
|
|
||||||
_updatedAt: { type: 'date', optional: true },
|
|
||||||
status: { type: 'int', optional: true },
|
|
||||||
pinned: { type: 'bool', optional: true },
|
|
||||||
starred: { type: 'bool', optional: true },
|
|
||||||
editedBy: 'messagesEditedBy',
|
|
||||||
reactions: { type: 'list', objectType: 'messagesReactions' },
|
|
||||||
role: { type: 'string', optional: true },
|
|
||||||
replies: 'string[]',
|
|
||||||
mentions: { type: 'list', objectType: 'users' },
|
|
||||||
channels: { type: 'list', objectType: 'rooms' },
|
|
||||||
unread: { type: 'bool', optional: true },
|
|
||||||
autoTranslate: { type: 'bool', default: false },
|
|
||||||
translations: { type: 'list', objectType: 'messagesTranslations' }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const frequentlyUsedEmojiSchema = {
|
|
||||||
name: 'frequentlyUsedEmoji',
|
|
||||||
primaryKey: 'content',
|
|
||||||
properties: {
|
|
||||||
content: { type: 'string', optional: true },
|
|
||||||
extension: { type: 'string', optional: true },
|
|
||||||
isCustom: 'bool',
|
|
||||||
count: 'int'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const slashCommandSchema = {
|
|
||||||
name: 'slashCommand',
|
|
||||||
primaryKey: 'command',
|
|
||||||
properties: {
|
|
||||||
command: 'string',
|
|
||||||
params: { type: 'string', optional: true },
|
|
||||||
description: { type: 'string', optional: true },
|
|
||||||
clientOnly: { type: 'bool', optional: true },
|
|
||||||
providesPreview: { type: 'bool', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const customEmojisSchema = {
|
|
||||||
name: 'customEmojis',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
name: 'string',
|
|
||||||
aliases: 'string[]',
|
|
||||||
extension: 'string',
|
|
||||||
_updatedAt: { type: 'date', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const rolesSchema = {
|
|
||||||
name: 'roles',
|
|
||||||
primaryKey: '_id',
|
|
||||||
properties: {
|
|
||||||
_id: 'string',
|
|
||||||
description: { type: 'string', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadsSchema = {
|
|
||||||
name: 'uploads',
|
|
||||||
primaryKey: 'path',
|
|
||||||
properties: {
|
|
||||||
path: 'string',
|
|
||||||
rid: 'string',
|
|
||||||
name: { type: 'string', optional: true },
|
|
||||||
description: { type: 'string', optional: true },
|
|
||||||
size: { type: 'int', optional: true },
|
|
||||||
type: { type: 'string', optional: true },
|
|
||||||
store: { type: 'string', optional: true },
|
|
||||||
progress: { type: 'int', default: 1 },
|
|
||||||
error: { type: 'bool', default: false }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const usersTypingSchema = {
|
|
||||||
name: 'usersTyping',
|
|
||||||
properties: {
|
|
||||||
rid: { type: 'string', indexed: true },
|
|
||||||
username: { type: 'string', optional: true }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const activeUsersSchema = {
|
|
||||||
name: 'activeUsers',
|
|
||||||
primaryKey: 'id',
|
|
||||||
properties: {
|
|
||||||
id: 'string',
|
|
||||||
name: 'string?',
|
|
||||||
username: 'string?',
|
|
||||||
status: 'string?',
|
|
||||||
utcOffset: 'double?'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const schema = [
|
|
||||||
settingsSchema,
|
|
||||||
subscriptionSchema,
|
|
||||||
messagesSchema,
|
|
||||||
threadsSchema,
|
|
||||||
threadMessagesSchema,
|
|
||||||
usersSchema,
|
|
||||||
roomsSchema,
|
|
||||||
attachment,
|
|
||||||
attachmentFields,
|
|
||||||
messagesEditedBySchema,
|
|
||||||
permissionsSchema,
|
|
||||||
url,
|
|
||||||
frequentlyUsedEmojiSchema,
|
|
||||||
customEmojisSchema,
|
|
||||||
messagesReactionsSchema,
|
|
||||||
rolesSchema,
|
|
||||||
uploadsSchema,
|
|
||||||
slashCommandSchema,
|
|
||||||
messagesTranslationsSchema
|
|
||||||
];
|
|
||||||
|
|
||||||
const inMemorySchema = [usersTypingSchema, activeUsersSchema];
|
|
||||||
|
|
||||||
class DB {
|
|
||||||
databases = {
|
|
||||||
serversDB: new Realm({
|
|
||||||
path: `${ RNRealmPath.realmPath }default.realm`,
|
|
||||||
schema: [
|
|
||||||
userSchema,
|
|
||||||
serversSchema
|
|
||||||
],
|
|
||||||
schemaVersion: 10,
|
|
||||||
migration: (oldRealm, newRealm) => {
|
|
||||||
if (oldRealm.schemaVersion >= 1 && newRealm.schemaVersion <= 9) {
|
|
||||||
const newServers = newRealm.objects('servers');
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-plusplus
|
|
||||||
for (let i = 0; i < newServers.length; i++) {
|
|
||||||
newServers[i].roomsUpdatedAt = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
inMemoryDB: new Realm({
|
|
||||||
path: `${ RNRealmPath.realmPath }memory.realm`,
|
|
||||||
schema: inMemorySchema,
|
|
||||||
schemaVersion: 2,
|
|
||||||
inMemory: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteAll(...args) {
|
|
||||||
return this.database.write(() => this.database.deleteAll(...args));
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(...args) {
|
|
||||||
return this.database.delete(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
write(...args) {
|
|
||||||
return this.database.write(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
create(...args) {
|
|
||||||
return this.database.create(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
objects(...args) {
|
|
||||||
return this.database.objects(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
objectForPrimaryKey(...args) {
|
|
||||||
return this.database.objectForPrimaryKey(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
get database() {
|
|
||||||
return this.databases.activeDB;
|
|
||||||
}
|
|
||||||
|
|
||||||
get memoryDatabase() {
|
|
||||||
return this.databases.inMemoryDB;
|
|
||||||
}
|
|
||||||
|
|
||||||
setActiveDB(database = '') {
|
|
||||||
const path = database.replace(/(^\w+:|^)\/\//, '');
|
|
||||||
return this.databases.activeDB = new Realm({
|
|
||||||
path: `${ RNRealmPath.realmPath }${ path }.realm`,
|
|
||||||
schema,
|
|
||||||
schemaVersion: 14,
|
|
||||||
migration: (oldRealm, newRealm) => {
|
|
||||||
if (oldRealm.schemaVersion >= 3 && newRealm.schemaVersion <= 13) {
|
|
||||||
const newSubs = newRealm.objects('subscriptions');
|
|
||||||
newRealm.delete(newSubs);
|
|
||||||
const newMessages = newRealm.objects('messages');
|
|
||||||
newRealm.delete(newMessages);
|
|
||||||
const newThreads = newRealm.objects('threads');
|
|
||||||
newRealm.delete(newThreads);
|
|
||||||
const newThreadMessages = newRealm.objects('threadMessages');
|
|
||||||
newRealm.delete(newThreadMessages);
|
|
||||||
}
|
|
||||||
if (newRealm.schemaVersion === 9) {
|
|
||||||
const newEmojis = newRealm.objects('customEmojis');
|
|
||||||
newRealm.delete(newEmojis);
|
|
||||||
const newSettings = newRealm.objects('settings');
|
|
||||||
newRealm.delete(newSettings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const db = new DB();
|
|
||||||
export default db;
|
|
||||||
|
|
||||||
// Realm workaround for "Cannot create asynchronous query while in a write transaction"
|
|
||||||
// inpired from https://github.com/realm/realm-js/issues/1188#issuecomment-359223918
|
|
||||||
export function safeAddListener(results, callback, database = db) {
|
|
||||||
if (!results || !results.addListener) {
|
|
||||||
console.log('⚠️ safeAddListener called for non addListener-compliant object');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (database.isInTransaction) {
|
|
||||||
setTimeout(() => {
|
|
||||||
safeAddListener(results, callback);
|
|
||||||
}, 50);
|
|
||||||
} else {
|
|
||||||
results.addListener(callback);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,12 +2,13 @@ import { AsyncStorage, InteractionManager } from 'react-native';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
||||||
import RNUserDefaults from 'rn-user-defaults';
|
import RNUserDefaults from 'rn-user-defaults';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
import * as FileSystem from 'expo-file-system';
|
import * as FileSystem from 'expo-file-system';
|
||||||
|
|
||||||
import reduxStore from './createStore';
|
import reduxStore from './createStore';
|
||||||
import defaultSettings from '../constants/settings';
|
import defaultSettings from '../constants/settings';
|
||||||
import messagesStatus from '../constants/messagesStatus';
|
import messagesStatus from '../constants/messagesStatus';
|
||||||
import database from './realm';
|
import database from './database';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import { isIOS, getBundleId } from '../utils/deviceInfo';
|
import { isIOS, getBundleId } from '../utils/deviceInfo';
|
||||||
import { extractHostname } from '../utils/server';
|
import { extractHostname } from '../utils/server';
|
||||||
|
@ -29,7 +30,7 @@ import getSettings from './methods/getSettings';
|
||||||
|
|
||||||
import getRooms from './methods/getRooms';
|
import getRooms from './methods/getRooms';
|
||||||
import getPermissions from './methods/getPermissions';
|
import getPermissions from './methods/getPermissions';
|
||||||
import getCustomEmojis from './methods/getCustomEmojis';
|
import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis';
|
||||||
import getSlashCommands from './methods/getSlashCommands';
|
import getSlashCommands from './methods/getSlashCommands';
|
||||||
import getRoles from './methods/getRoles';
|
import getRoles from './methods/getRoles';
|
||||||
import canOpenRoom from './methods/canOpenRoom';
|
import canOpenRoom from './methods/canOpenRoom';
|
||||||
|
@ -43,6 +44,7 @@ import { sendFileMessage, cancelUpload, isUploadActive } from './methods/sendFil
|
||||||
|
|
||||||
import { getDeviceToken } from '../notifications/push';
|
import { getDeviceToken } from '../notifications/push';
|
||||||
import { SERVERS, SERVER_URL } from '../constants/userDefaults';
|
import { SERVERS, SERVER_URL } from '../constants/userDefaults';
|
||||||
|
import { setActiveUsers } from '../actions/activeUsers';
|
||||||
|
|
||||||
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
||||||
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
|
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
|
||||||
|
@ -59,7 +61,11 @@ const RocketChat = {
|
||||||
if (this.roomsSub) {
|
if (this.roomsSub) {
|
||||||
this.roomsSub.stop();
|
this.roomsSub.stop();
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
this.roomsSub = await subscribeRooms.call(this);
|
this.roomsSub = await subscribeRooms.call(this);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
subscribeRoom,
|
subscribeRoom,
|
||||||
canOpenRoom,
|
canOpenRoom,
|
||||||
|
@ -100,67 +106,36 @@ const RocketChat = {
|
||||||
message: 'The_URL_is_invalid'
|
message: 'The_URL_is_invalid'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
_setUser(ddpMessage) {
|
stopListener(listener) {
|
||||||
this.activeUsers = this.activeUsers || {};
|
return listener && listener.stop();
|
||||||
const { user } = reduxStore.getState().login;
|
|
||||||
|
|
||||||
if (ddpMessage.fields && user && user.id === ddpMessage.id) {
|
|
||||||
reduxStore.dispatch(setUser(ddpMessage.fields));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ddpMessage.cleared && user && user.id === ddpMessage.id) {
|
|
||||||
reduxStore.dispatch(setUser({ status: 'offline' }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._setUserTimer) {
|
|
||||||
this._setUserTimer = setTimeout(() => {
|
|
||||||
const batchUsers = this.activeUsers;
|
|
||||||
InteractionManager.runAfterInteractions(() => {
|
|
||||||
database.memoryDatabase.write(() => {
|
|
||||||
Object.keys(batchUsers).forEach((key) => {
|
|
||||||
if (batchUsers[key] && batchUsers[key].id) {
|
|
||||||
try {
|
|
||||||
const data = batchUsers[key];
|
|
||||||
if (data.removed) {
|
|
||||||
const userRecord = database.memoryDatabase.objectForPrimaryKey('activeUsers', data.id);
|
|
||||||
if (userRecord) {
|
|
||||||
userRecord.status = 'offline';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
database.memoryDatabase.create('activeUsers', data, true);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this._setUserTimer = null;
|
|
||||||
return this.activeUsers = {};
|
|
||||||
}, 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ddpMessage.fields) {
|
|
||||||
this.activeUsers[ddpMessage.id] = {
|
|
||||||
id: ddpMessage.id,
|
|
||||||
removed: true
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
this.activeUsers[ddpMessage.id] = {
|
|
||||||
id: ddpMessage.id, ...this.activeUsers[ddpMessage.id], ...ddpMessage.fields
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
connect({ server, user }) {
|
connect({ server, user }) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
if (!this.sdk || this.sdk.client.host !== server) {
|
||||||
database.setActiveDB(server);
|
database.setActiveDB(server);
|
||||||
|
}
|
||||||
reduxStore.dispatch(connectRequest());
|
reduxStore.dispatch(connectRequest());
|
||||||
|
|
||||||
if (this.connectTimeout) {
|
if (this.connectTimeout) {
|
||||||
clearTimeout(this.connectTimeout);
|
clearTimeout(this.connectTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.connectedListener) {
|
||||||
|
this.connectedListener.then(this.stopListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.closeListener) {
|
||||||
|
this.closeListener.then(this.stopListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.usersListener) {
|
||||||
|
this.usersListener.then(this.stopListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.notifyLoggedListener) {
|
||||||
|
this.notifyLoggedListener.then(this.stopListener);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.roomsSub) {
|
if (this.roomsSub) {
|
||||||
this.roomsSub.stop();
|
this.roomsSub.stop();
|
||||||
}
|
}
|
||||||
|
@ -191,35 +166,37 @@ const RocketChat = {
|
||||||
}, 10000);
|
}, 10000);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sdk.onStreamData('connected', () => {
|
this.connectedListener = this.sdk.onStreamData('connected', () => {
|
||||||
reduxStore.dispatch(connectSuccess());
|
reduxStore.dispatch(connectSuccess());
|
||||||
// const { isAuthenticated } = reduxStore.getState().login;
|
|
||||||
// if (isAuthenticated) {
|
|
||||||
// this.getUserPresence();
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sdk.onStreamData('close', () => {
|
this.closeListener = this.sdk.onStreamData('close', () => {
|
||||||
reduxStore.dispatch(disconnect());
|
reduxStore.dispatch(disconnect());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sdk.onStreamData('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage)));
|
this.usersListener = this.sdk.onStreamData('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage)));
|
||||||
|
|
||||||
this.sdk.onStreamData('stream-notify-logged', protectedFunction((ddpMessage) => {
|
this.notifyLoggedListener = this.sdk.onStreamData('stream-notify-logged', protectedFunction((ddpMessage) => {
|
||||||
const { eventName } = ddpMessage.fields;
|
const { eventName } = ddpMessage.fields;
|
||||||
if (eventName === 'user-status') {
|
if (eventName === 'user-status') {
|
||||||
const userStatus = ddpMessage.fields.args[0];
|
this.activeUsers = this.activeUsers || {};
|
||||||
const [id, username, status] = userStatus;
|
if (!this._setUserTimer) {
|
||||||
if (username) {
|
this._setUserTimer = setTimeout(() => {
|
||||||
database.memoryDatabase.write(() => {
|
const activeUsersBatch = this.activeUsers;
|
||||||
try {
|
InteractionManager.runAfterInteractions(() => {
|
||||||
database.memoryDatabase.create('activeUsers', {
|
reduxStore.dispatch(setActiveUsers(activeUsersBatch));
|
||||||
id, username, status: STATUSES[status]
|
|
||||||
}, true);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
this._setUserTimer = null;
|
||||||
|
return this.activeUsers = {};
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
const userStatus = ddpMessage.fields.args[0];
|
||||||
|
const [id,, status] = userStatus;
|
||||||
|
this.activeUsers[id] = STATUSES[status];
|
||||||
|
|
||||||
|
const { user: loggedUser } = reduxStore.getState().login;
|
||||||
|
if (loggedUser && loggedUser.id === id) {
|
||||||
|
reduxStore.dispatch(setUser({ status: STATUSES[status] }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -242,19 +219,31 @@ const RocketChat = {
|
||||||
this.sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl });
|
this.sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl });
|
||||||
|
|
||||||
// set Server
|
// set Server
|
||||||
const { serversDB } = database.databases;
|
const serversDB = database.servers;
|
||||||
reduxStore.dispatch(shareSelectServer(server));
|
reduxStore.dispatch(shareSelectServer(server));
|
||||||
|
|
||||||
// set User info
|
// set User info
|
||||||
|
try {
|
||||||
const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
|
const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
|
||||||
const user = userId && serversDB.objectForPrimaryKey('user', userId);
|
const userCollections = serversDB.collections.get('users');
|
||||||
|
let user = null;
|
||||||
|
if (userId) {
|
||||||
|
user = await userCollections.find(userId);
|
||||||
|
user = {
|
||||||
|
id: user.id,
|
||||||
|
token: user.token,
|
||||||
|
username: user.username
|
||||||
|
};
|
||||||
|
}
|
||||||
reduxStore.dispatch(shareSetUser({
|
reduxStore.dispatch(shareSetUser({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
token: user.token,
|
token: user.token,
|
||||||
username: user.username
|
username: user.username
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await RocketChat.login({ resume: user.token });
|
await RocketChat.login({ resume: user.token });
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
register(credentials) {
|
register(credentials) {
|
||||||
|
@ -376,23 +365,26 @@ const RocketChat = {
|
||||||
console.log('logout_rn_user_defaults', error);
|
console.log('logout_rn_user_defaults', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { serversDB } = database.databases;
|
|
||||||
|
|
||||||
const userId = await RNUserDefaults.get(`${ TOKEN_KEY }-${ server }`);
|
const userId = await RNUserDefaults.get(`${ TOKEN_KEY }-${ server }`);
|
||||||
|
|
||||||
serversDB.write(() => {
|
try {
|
||||||
const user = serversDB.objectForPrimaryKey('user', userId);
|
const serversDB = database.servers;
|
||||||
serversDB.delete(user);
|
await serversDB.action(async() => {
|
||||||
|
const usersCollection = serversDB.collections.get('users');
|
||||||
|
const user = await usersCollection.find(userId);
|
||||||
|
await user.destroyPermanently();
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all([
|
await RNUserDefaults.clear('currentServer');
|
||||||
RNUserDefaults.clear('currentServer'),
|
await RNUserDefaults.clear(TOKEN_KEY);
|
||||||
RNUserDefaults.clear(TOKEN_KEY),
|
await RNUserDefaults.clear(`${ TOKEN_KEY }-${ server }`);
|
||||||
RNUserDefaults.clear(`${ TOKEN_KEY }-${ server }`)
|
|
||||||
]).catch(error => console.log(error));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
database.deleteAll();
|
const db = database.active;
|
||||||
|
await db.action(() => db.unsafeResetDatabase());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
@ -432,19 +424,21 @@ const RocketChat = {
|
||||||
sendMessage,
|
sendMessage,
|
||||||
getRooms,
|
getRooms,
|
||||||
readMessages,
|
readMessages,
|
||||||
async resendMessage(messageId) {
|
async resendMessage(message) {
|
||||||
const message = await database.objects('messages').filtered('_id = $0', messageId)[0];
|
const db = database.active;
|
||||||
try {
|
try {
|
||||||
database.write(() => {
|
await db.action(async() => {
|
||||||
message.status = messagesStatus.TEMP;
|
await message.update((m) => {
|
||||||
database.create('messages', message, true);
|
m.status = messagesStatus.TEMP;
|
||||||
});
|
});
|
||||||
await sendMessageCall.call(this, JSON.parse(JSON.stringify(message)));
|
});
|
||||||
|
await sendMessageCall.call(this, message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
try {
|
try {
|
||||||
database.write(() => {
|
await db.action(async() => {
|
||||||
message.status = messagesStatus.ERROR;
|
await message.update((m) => {
|
||||||
database.create('messages', message, true);
|
m.status = messagesStatus.ERROR;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
|
@ -464,12 +458,15 @@ const RocketChat = {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = database.objects('subscriptions').filtered('name CONTAINS[c] $0', searchText);
|
const db = database.active;
|
||||||
|
let data = await db.collections.get('subscriptions').query(
|
||||||
|
Q.where('name', Q.like(`%${ Q.sanitizeLikeString(searchText) }%`))
|
||||||
|
).fetch();
|
||||||
|
|
||||||
if (filterUsers && !filterRooms) {
|
if (filterUsers && !filterRooms) {
|
||||||
data = data.filtered('t = $0', 'd');
|
data = data.filter(item => item.t === 'd');
|
||||||
} else if (!filterUsers && filterRooms) {
|
} else if (!filterUsers && filterRooms) {
|
||||||
data = data.filtered('t != $0', 'd');
|
data = data.filter(item => item.t !== 'd');
|
||||||
}
|
}
|
||||||
data = data.slice(0, 7);
|
data = data.slice(0, 7);
|
||||||
|
|
||||||
|
@ -525,6 +522,7 @@ const RocketChat = {
|
||||||
getSettings,
|
getSettings,
|
||||||
getPermissions,
|
getPermissions,
|
||||||
getCustomEmojis,
|
getCustomEmojis,
|
||||||
|
setCustomEmojis,
|
||||||
getSlashCommands,
|
getSlashCommands,
|
||||||
getRoles,
|
getRoles,
|
||||||
parseSettings: settings => settings.reduce((ret, item) => {
|
parseSettings: settings => settings.reduce((ret, item) => {
|
||||||
|
@ -537,46 +535,47 @@ const RocketChat = {
|
||||||
return setting;
|
return setting;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteMessage(message) {
|
deleteMessage(messageId, rid) {
|
||||||
const { _id, rid } = message;
|
|
||||||
// RC 0.48.0
|
// RC 0.48.0
|
||||||
return this.sdk.post('chat.delete', { roomId: rid, msgId: _id });
|
return this.sdk.post('chat.delete', { msgId: messageId, roomId: rid });
|
||||||
},
|
},
|
||||||
editMessage(message) {
|
editMessage(message) {
|
||||||
const { _id, msg, rid } = message;
|
const { id, msg, rid } = message;
|
||||||
// RC 0.49.0
|
// RC 0.49.0
|
||||||
return this.sdk.post('chat.update', { roomId: rid, msgId: _id, text: msg });
|
return this.sdk.post('chat.update', { roomId: rid, msgId: id, text: msg });
|
||||||
},
|
},
|
||||||
toggleStarMessage(message) {
|
toggleStarMessage(messageId, starred) {
|
||||||
if (message.starred) {
|
if (starred) {
|
||||||
// RC 0.59.0
|
// RC 0.59.0
|
||||||
return this.sdk.post('chat.unStarMessage', { messageId: message._id });
|
return this.sdk.post('chat.unStarMessage', { messageId });
|
||||||
}
|
}
|
||||||
// RC 0.59.0
|
// RC 0.59.0
|
||||||
return this.sdk.post('chat.starMessage', { messageId: message._id });
|
return this.sdk.post('chat.starMessage', { messageId });
|
||||||
},
|
},
|
||||||
togglePinMessage(message) {
|
togglePinMessage(messageId, pinned) {
|
||||||
if (message.pinned) {
|
if (pinned) {
|
||||||
// RC 0.59.0
|
// RC 0.59.0
|
||||||
return this.sdk.post('chat.unPinMessage', { messageId: message._id });
|
return this.sdk.post('chat.unPinMessage', { messageId });
|
||||||
}
|
}
|
||||||
// RC 0.59.0
|
// RC 0.59.0
|
||||||
return this.sdk.post('chat.pinMessage', { messageId: message._id });
|
return this.sdk.post('chat.pinMessage', { messageId });
|
||||||
},
|
},
|
||||||
reportMessage(messageId) {
|
reportMessage(messageId) {
|
||||||
return this.sdk.post('chat.reportMessage', { messageId, description: 'Message reported by user' });
|
return this.sdk.post('chat.reportMessage', { messageId, description: 'Message reported by user' });
|
||||||
},
|
},
|
||||||
getRoom(rid) {
|
async getRoom(rid) {
|
||||||
const [result] = database.objects('subscriptions').filtered('rid = $0', rid);
|
try {
|
||||||
if (!result) {
|
const db = database.active;
|
||||||
|
const room = await db.collections.get('subscriptions').find(rid);
|
||||||
|
return Promise.resolve(room);
|
||||||
|
} catch (error) {
|
||||||
return Promise.reject(new Error('Room not found'));
|
return Promise.reject(new Error('Room not found'));
|
||||||
}
|
}
|
||||||
return Promise.resolve(result);
|
|
||||||
},
|
},
|
||||||
async getPermalinkMessage(message) {
|
async getPermalinkMessage(message) {
|
||||||
let room;
|
let room;
|
||||||
try {
|
try {
|
||||||
room = await RocketChat.getRoom(message.rid);
|
room = await RocketChat.getRoom(message.subscription.id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
return null;
|
return null;
|
||||||
|
@ -587,7 +586,7 @@ const RocketChat = {
|
||||||
c: 'channel',
|
c: 'channel',
|
||||||
d: 'direct'
|
d: 'direct'
|
||||||
}[room.t];
|
}[room.t];
|
||||||
return `${ server }/${ roomType }/${ room.name }?msg=${ message._id }`;
|
return `${ server }/${ roomType }/${ room.name }?msg=${ message.id }`;
|
||||||
},
|
},
|
||||||
getPermalinkChannel(channel) {
|
getPermalinkChannel(channel) {
|
||||||
const { server } = reduxStore.getState().server;
|
const { server } = reduxStore.getState().server;
|
||||||
|
@ -725,25 +724,26 @@ const RocketChat = {
|
||||||
// RC 0.57.0
|
// RC 0.57.0
|
||||||
return this.sdk.methodCall('getSingleMessage', msgId);
|
return this.sdk.methodCall('getSingleMessage', msgId);
|
||||||
},
|
},
|
||||||
hasPermission(permissions, rid) {
|
async hasPermission(permissions, rid) {
|
||||||
|
const db = database.active;
|
||||||
|
const subsCollection = db.collections.get('subscriptions');
|
||||||
|
const permissionsCollection = db.collections.get('permissions');
|
||||||
let roomRoles = [];
|
let roomRoles = [];
|
||||||
try {
|
try {
|
||||||
// get the room from realm
|
// get the room from database
|
||||||
const [room] = database.objects('subscriptions').filtered('rid = $0', rid);
|
const room = await subsCollection.find(rid);
|
||||||
if (!room) {
|
// get room roles
|
||||||
|
roomRoles = room.roles;
|
||||||
|
} catch (error) {
|
||||||
|
console.log('hasPermission -> Room not found');
|
||||||
return permissions.reduce((result, permission) => {
|
return permissions.reduce((result, permission) => {
|
||||||
result[permission] = false;
|
result[permission] = false;
|
||||||
return result;
|
return result;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
// get room roles
|
// get permissions from database
|
||||||
roomRoles = room.roles;
|
try {
|
||||||
} catch (error) {
|
const permissionsFiltered = await permissionsCollection.query(Q.where('id', Q.oneOf(permissions))).fetch();
|
||||||
console.log('hasPermission -> error', error);
|
|
||||||
}
|
|
||||||
// get permissions from realm
|
|
||||||
const permissionsFiltered = database.objects('permissions')
|
|
||||||
.filter(permission => permissions.includes(permission._id));
|
|
||||||
// get user roles on the server from redux
|
// get user roles on the server from redux
|
||||||
const userRoles = (reduxStore.getState().login.user && reduxStore.getState().login.user.roles) || [];
|
const userRoles = (reduxStore.getState().login.user && reduxStore.getState().login.user.roles) || [];
|
||||||
// merge both roles
|
// merge both roles
|
||||||
|
@ -753,12 +753,15 @@ const RocketChat = {
|
||||||
// e.g. { 'edit-room': true, 'set-readonly': false }
|
// e.g. { 'edit-room': true, 'set-readonly': false }
|
||||||
return permissions.reduce((result, permission) => {
|
return permissions.reduce((result, permission) => {
|
||||||
result[permission] = false;
|
result[permission] = false;
|
||||||
const permissionFound = permissionsFiltered.find(p => p._id === permission);
|
const permissionFound = permissionsFiltered.find(p => p.id === permission);
|
||||||
if (permissionFound) {
|
if (permissionFound) {
|
||||||
result[permission] = returnAnArray(permissionFound.roles).some(r => mergedRoles.includes(r));
|
result[permission] = returnAnArray(permissionFound.roles).some(r => mergedRoles.includes(r));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}, {});
|
}, {});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getAvatarSuggestion() {
|
getAvatarSuggestion() {
|
||||||
// RC 0.51.0
|
// RC 0.51.0
|
||||||
|
@ -927,6 +930,35 @@ const RocketChat = {
|
||||||
command, params, roomId, previewItem
|
command, params, roomId, previewItem
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
_setUser(ddpMessage) {
|
||||||
|
this.activeUsers = this.activeUsers || {};
|
||||||
|
const { user } = reduxStore.getState().login;
|
||||||
|
|
||||||
|
if (ddpMessage.fields && user && user.id === ddpMessage.id) {
|
||||||
|
reduxStore.dispatch(setUser(ddpMessage.fields));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ddpMessage.cleared && user && user.id === ddpMessage.id) {
|
||||||
|
reduxStore.dispatch(setUser({ status: 'offline' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._setUserTimer) {
|
||||||
|
this._setUserTimer = setTimeout(() => {
|
||||||
|
const activeUsersBatch = this.activeUsers;
|
||||||
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
reduxStore.dispatch(setActiveUsers(activeUsersBatch));
|
||||||
|
});
|
||||||
|
this._setUserTimer = null;
|
||||||
|
return this.activeUsers = {};
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ddpMessage.fields) {
|
||||||
|
this.activeUsers[ddpMessage.id] = 'offline';
|
||||||
|
} else if (ddpMessage.fields.status) {
|
||||||
|
this.activeUsers[ddpMessage.id] = ddpMessage.fields.status;
|
||||||
|
}
|
||||||
|
},
|
||||||
getUserPresence() {
|
getUserPresence() {
|
||||||
return new Promise(async(resolve) => {
|
return new Promise(async(resolve) => {
|
||||||
const serverVersion = reduxStore.getState().server.version;
|
const serverVersion = reduxStore.getState().server.version;
|
||||||
|
@ -950,16 +982,12 @@ const RocketChat = {
|
||||||
// RC 1.1.0
|
// RC 1.1.0
|
||||||
const result = await this.sdk.get('users.presence', params);
|
const result = await this.sdk.get('users.presence', params);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
// this.lastUserPresenceFetch = new Date();
|
const activeUsers = result.users.reduce((ret, item) => {
|
||||||
database.memoryDatabase.write(() => {
|
ret[item._id] = item.status;
|
||||||
result.users.forEach((item) => {
|
return ret;
|
||||||
try {
|
}, {});
|
||||||
item.id = item._id;
|
InteractionManager.runAfterInteractions(() => {
|
||||||
database.memoryDatabase.create('activeUsers', item, true);
|
reduxStore.dispatch(setActiveUsers(activeUsers));
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
this.sdk.subscribe('stream-notify-logged', 'user-status');
|
this.sdk.subscribe('stream-notify-logged', 'user-status');
|
||||||
return resolve();
|
return resolve();
|
||||||
|
@ -975,13 +1003,15 @@ const RocketChat = {
|
||||||
query, count, offset, sort
|
query, count, offset, sort
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
canAutoTranslate() {
|
async canAutoTranslate() {
|
||||||
|
const db = database.active;
|
||||||
try {
|
try {
|
||||||
const AutoTranslate_Enabled = reduxStore.getState().settings && reduxStore.getState().settings.AutoTranslate_Enabled;
|
const AutoTranslate_Enabled = reduxStore.getState().settings && reduxStore.getState().settings.AutoTranslate_Enabled;
|
||||||
if (!AutoTranslate_Enabled) {
|
if (!AutoTranslate_Enabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const autoTranslatePermission = database.objectForPrimaryKey('permissions', 'auto-translate');
|
const permissionsCollection = db.collections.get('permissions');
|
||||||
|
const autoTranslatePermission = await permissionsCollection.find('auto-translate');
|
||||||
const userRoles = (reduxStore.getState().login.user && reduxStore.getState().login.user.roles) || [];
|
const userRoles = (reduxStore.getState().login.user && reduxStore.getState().login.user.roles) || [];
|
||||||
return autoTranslatePermission.roles.some(role => userRoles.includes(role));
|
return autoTranslatePermission.roles.some(role => userRoles.includes(role));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ const formatMsg = ({
|
||||||
let prefix = '';
|
let prefix = '';
|
||||||
const isLastMessageSentByMe = lastMessage.u.username === username;
|
const isLastMessageSentByMe = lastMessage.u.username === username;
|
||||||
|
|
||||||
if (!lastMessage.msg && Object.keys(lastMessage.attachments).length) {
|
if (!lastMessage.msg && lastMessage.attachments && Object.keys(lastMessage.attachments).length) {
|
||||||
const user = isLastMessageSentByMe ? I18n.t('You') : lastMessage.u.username;
|
const user = isLastMessageSentByMe ? I18n.t('You') : lastMessage.u.username;
|
||||||
return I18n.t('User_sent_an_attachment', { user });
|
return I18n.t('User_sent_an_attachment', { user });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Status from '../../containers/Status';
|
import Status from '../../containers/Status/Status';
|
||||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
const TypeIcon = React.memo(({ type, id, prid }) => {
|
const TypeIcon = React.memo(({ type, prid, status }) => {
|
||||||
if (type === 'd') {
|
if (type === 'd') {
|
||||||
return <Status style={styles.status} size={10} id={id} />;
|
return <Status style={styles.status} size={10} status={status} />;
|
||||||
}
|
}
|
||||||
return <RoomTypeIcon type={prid ? 'discussion' : type} />;
|
return <RoomTypeIcon type={prid ? 'discussion' : type} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
TypeIcon.propTypes = {
|
TypeIcon.propTypes = {
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
id: PropTypes.string,
|
status: PropTypes.string,
|
||||||
prid: PropTypes.string
|
prid: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, Text, Animated } from 'react-native';
|
import { View, Text, Animated } from 'react-native';
|
||||||
import { RectButton, PanGestureHandler, State } from 'react-native-gesture-handler';
|
import {
|
||||||
|
RectButton,
|
||||||
|
PanGestureHandler,
|
||||||
|
State
|
||||||
|
} from 'react-native-gesture-handler';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import styles, {
|
import styles, {
|
||||||
ROW_HEIGHT, ACTION_WIDTH, SMALL_SWIPE, LONG_SWIPE
|
ROW_HEIGHT,
|
||||||
|
ACTION_WIDTH,
|
||||||
|
SMALL_SWIPE,
|
||||||
|
LONG_SWIPE
|
||||||
} from './styles';
|
} from './styles';
|
||||||
import UnreadBadge from './UnreadBadge';
|
import UnreadBadge from './UnreadBadge';
|
||||||
import TypeIcon from './TypeIcon';
|
import TypeIcon from './TypeIcon';
|
||||||
|
@ -16,9 +24,20 @@ import { LeftActions, RightActions } from './Actions';
|
||||||
|
|
||||||
export { ROW_HEIGHT };
|
export { ROW_HEIGHT };
|
||||||
|
|
||||||
const attrs = ['name', 'unread', 'userMentions', 'showLastMessage', 'alert', 'type', 'width', 'isRead', 'favorite'];
|
const attrs = [
|
||||||
|
'name',
|
||||||
|
'unread',
|
||||||
|
'userMentions',
|
||||||
|
'showLastMessage',
|
||||||
|
'alert',
|
||||||
|
'type',
|
||||||
|
'width',
|
||||||
|
'isRead',
|
||||||
|
'favorite',
|
||||||
|
'status'
|
||||||
|
];
|
||||||
|
|
||||||
export default class RoomItem extends React.Component {
|
class RoomItem extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
|
@ -41,17 +60,16 @@ export default class RoomItem extends React.Component {
|
||||||
favorite: PropTypes.bool,
|
favorite: PropTypes.bool,
|
||||||
isRead: PropTypes.bool,
|
isRead: PropTypes.bool,
|
||||||
rid: PropTypes.string,
|
rid: PropTypes.string,
|
||||||
|
status: PropTypes.string,
|
||||||
toggleFav: PropTypes.func,
|
toggleFav: PropTypes.func,
|
||||||
toggleRead: PropTypes.func,
|
toggleRead: PropTypes.func,
|
||||||
hideChannel: PropTypes.func
|
hideChannel: PropTypes.func
|
||||||
}
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
avatarSize: 48
|
avatarSize: 48
|
||||||
}
|
};
|
||||||
|
|
||||||
// Making jest happy: https://github.com/facebook/react-native/issues/22175
|
|
||||||
// eslint-disable-next-line no-useless-constructor
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.dragX = new Animated.Value(0);
|
this.dragX = new Animated.Value(0);
|
||||||
|
@ -70,13 +88,7 @@ export default class RoomItem extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
const { lastMessage, _updatedAt } = this.props;
|
const { _updatedAt } = this.props;
|
||||||
const oldlastMessage = lastMessage;
|
|
||||||
const newLastmessage = nextProps.lastMessage;
|
|
||||||
|
|
||||||
if (oldlastMessage && newLastmessage && oldlastMessage.ts !== newLastmessage.ts) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (_updatedAt && nextProps._updatedAt && nextProps._updatedAt.toISOString() !== _updatedAt.toISOString()) {
|
if (_updatedAt && nextProps._updatedAt && nextProps._updatedAt.toISOString() !== _updatedAt.toISOString()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -165,31 +177,31 @@ export default class RoomItem extends React.Component {
|
||||||
toggleFav(rid, favorite);
|
toggleFav(rid, favorite);
|
||||||
}
|
}
|
||||||
this.close();
|
this.close();
|
||||||
}
|
};
|
||||||
|
|
||||||
toggleRead = () => {
|
toggleRead = () => {
|
||||||
const { toggleRead, rid, isRead } = this.props;
|
const { toggleRead, rid, isRead } = this.props;
|
||||||
if (toggleRead) {
|
if (toggleRead) {
|
||||||
toggleRead(rid, isRead);
|
toggleRead(rid, isRead);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
hideChannel = () => {
|
hideChannel = () => {
|
||||||
const { hideChannel, rid, type } = this.props;
|
const { hideChannel, rid, type } = this.props;
|
||||||
if (hideChannel) {
|
if (hideChannel) {
|
||||||
hideChannel(rid, type);
|
hideChannel(rid, type);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onToggleReadPress = () => {
|
onToggleReadPress = () => {
|
||||||
this.toggleRead();
|
this.toggleRead();
|
||||||
this.close();
|
this.close();
|
||||||
}
|
};
|
||||||
|
|
||||||
onHidePress = () => {
|
onHidePress = () => {
|
||||||
this.hideChannel();
|
this.hideChannel();
|
||||||
this.close();
|
this.close();
|
||||||
}
|
};
|
||||||
|
|
||||||
onPress = () => {
|
onPress = () => {
|
||||||
const { rowState } = this.state;
|
const { rowState } = this.state;
|
||||||
|
@ -201,11 +213,11 @@ export default class RoomItem extends React.Component {
|
||||||
if (onPress) {
|
if (onPress) {
|
||||||
onPress();
|
onPress();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
unread, userMentions, name, _updatedAt, alert, testID, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, lastMessage, isRead, width, favorite
|
unread, userMentions, name, _updatedAt, alert, testID, type, avatarSize, baseUrl, userId, username, token, id, prid, showLastMessage, lastMessage, isRead, width, favorite, status
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const date = formatDate(_updatedAt);
|
const date = formatDate(_updatedAt);
|
||||||
|
@ -246,11 +258,9 @@ export default class RoomItem extends React.Component {
|
||||||
onHidePress={this.onHidePress}
|
onHidePress={this.onHidePress}
|
||||||
/>
|
/>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={
|
style={{
|
||||||
{
|
|
||||||
transform: [{ translateX: this.transX }]
|
transform: [{ translateX: this.transX }]
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<RectButton
|
<RectButton
|
||||||
onPress={this.onPress}
|
onPress={this.onPress}
|
||||||
|
@ -263,16 +273,59 @@ export default class RoomItem extends React.Component {
|
||||||
style={styles.container}
|
style={styles.container}
|
||||||
accessibilityLabel={accessibilityLabel}
|
accessibilityLabel={accessibilityLabel}
|
||||||
>
|
>
|
||||||
<Avatar text={name} size={avatarSize} type={type} baseUrl={baseUrl} style={styles.avatar} userId={userId} token={token} />
|
<Avatar
|
||||||
|
text={name}
|
||||||
|
size={avatarSize}
|
||||||
|
type={type}
|
||||||
|
baseUrl={baseUrl}
|
||||||
|
style={styles.avatar}
|
||||||
|
userId={userId}
|
||||||
|
token={token}
|
||||||
|
/>
|
||||||
<View style={styles.centerContainer}>
|
<View style={styles.centerContainer}>
|
||||||
<View style={styles.titleContainer}>
|
<View style={styles.titleContainer}>
|
||||||
<TypeIcon type={type} id={id} prid={prid} />
|
<TypeIcon
|
||||||
<Text style={[styles.title, alert && styles.alert]} ellipsizeMode='tail' numberOfLines={1}>{ name }</Text>
|
type={type}
|
||||||
{_updatedAt ? <Text style={[styles.date, alert && styles.updateAlert]} ellipsizeMode='tail' numberOfLines={1}>{ capitalize(date) }</Text> : null}
|
id={id}
|
||||||
|
prid={prid}
|
||||||
|
status={status}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.title,
|
||||||
|
alert && styles.alert
|
||||||
|
]}
|
||||||
|
ellipsizeMode='tail'
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
|
{_updatedAt ? (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.date,
|
||||||
|
alert && styles.updateAlert
|
||||||
|
]}
|
||||||
|
ellipsizeMode='tail'
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
|
{capitalize(date)}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<LastMessage lastMessage={lastMessage} type={type} showLastMessage={showLastMessage} username={username} alert={alert} />
|
<LastMessage
|
||||||
<UnreadBadge unread={unread} userMentions={userMentions} type={type} />
|
lastMessage={lastMessage}
|
||||||
|
type={type}
|
||||||
|
showLastMessage={showLastMessage}
|
||||||
|
username={username}
|
||||||
|
alert={alert}
|
||||||
|
/>
|
||||||
|
<UnreadBadge
|
||||||
|
unread={unread}
|
||||||
|
userMentions={userMentions}
|
||||||
|
type={type}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
@ -283,3 +336,9 @@ export default class RoomItem extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
|
status: state.meteor.connected && ownProps.type === 'd' ? state.activeUsers[ownProps.id] : 'offline'
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(RoomItem);
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { SET_ACTIVE_USERS } from '../actions/actionsTypes';
|
||||||
|
|
||||||
|
const initialState = {};
|
||||||
|
|
||||||
|
export default function activeUsers(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case SET_ACTIVE_USERS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.activeUsers
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ const initialState = {
|
||||||
error: {}
|
error: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function messages(state = initialState, action) {
|
export default function(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case CREATE_CHANNEL.REQUEST:
|
case CREATE_CHANNEL.REQUEST:
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { SET_CUSTOM_EMOJIS } from '../actions/actionsTypes';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
customEmojis: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function customEmojis(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case SET_CUSTOM_EMOJIS:
|
||||||
|
return action.emojis;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ import { combineReducers } from 'redux';
|
||||||
import settings from './reducers';
|
import settings from './reducers';
|
||||||
import login from './login';
|
import login from './login';
|
||||||
import meteor from './connect';
|
import meteor from './connect';
|
||||||
import messages from './messages';
|
|
||||||
import rooms from './rooms';
|
import rooms from './rooms';
|
||||||
import server from './server';
|
import server from './server';
|
||||||
import selectedUsers from './selectedUsers';
|
import selectedUsers from './selectedUsers';
|
||||||
|
@ -13,12 +12,14 @@ import notification from './notification';
|
||||||
import markdown from './markdown';
|
import markdown from './markdown';
|
||||||
import share from './share';
|
import share from './share';
|
||||||
import crashReport from './crashReport';
|
import crashReport from './crashReport';
|
||||||
|
import customEmojis from './customEmojis';
|
||||||
|
import activeUsers from './activeUsers';
|
||||||
|
import usersTyping from './usersTyping';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings,
|
settings,
|
||||||
login,
|
login,
|
||||||
meteor,
|
meteor,
|
||||||
messages,
|
|
||||||
server,
|
server,
|
||||||
selectedUsers,
|
selectedUsers,
|
||||||
createChannel,
|
createChannel,
|
||||||
|
@ -28,5 +29,8 @@ export default combineReducers({
|
||||||
notification,
|
notification,
|
||||||
markdown,
|
markdown,
|
||||||
share,
|
share,
|
||||||
crashReport
|
crashReport,
|
||||||
|
customEmojis,
|
||||||
|
activeUsers,
|
||||||
|
usersTyping
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
import * as types from '../actions/actionsTypes';
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
message: {},
|
|
||||||
actionMessage: {},
|
|
||||||
replyMessage: {},
|
|
||||||
replying: false,
|
|
||||||
editing: false,
|
|
||||||
showActions: false,
|
|
||||||
showErrorActions: false,
|
|
||||||
showReactionPicker: false
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function messages(state = initialState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case types.MESSAGES.ACTIONS_SHOW:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
showActions: true,
|
|
||||||
actionMessage: action.actionMessage
|
|
||||||
};
|
|
||||||
case types.MESSAGES.ACTIONS_HIDE:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
showActions: false
|
|
||||||
};
|
|
||||||
case types.MESSAGES.ERROR_ACTIONS_SHOW:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
showErrorActions: true,
|
|
||||||
actionMessage: action.actionMessage
|
|
||||||
};
|
|
||||||
case types.MESSAGES.ERROR_ACTIONS_HIDE:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
showErrorActions: false
|
|
||||||
};
|
|
||||||
case types.MESSAGES.EDIT_INIT:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
message: action.message,
|
|
||||||
editing: true
|
|
||||||
};
|
|
||||||
case types.MESSAGES.EDIT_CANCEL:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
message: {},
|
|
||||||
editing: false
|
|
||||||
};
|
|
||||||
case types.MESSAGES.EDIT_SUCCESS:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
message: {},
|
|
||||||
editing: false
|
|
||||||
};
|
|
||||||
case types.MESSAGES.EDIT_FAILURE:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
message: {},
|
|
||||||
editing: false
|
|
||||||
};
|
|
||||||
case types.MESSAGES.REPLY_INIT:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
replyMessage: {
|
|
||||||
...action.message,
|
|
||||||
mention: action.mention
|
|
||||||
},
|
|
||||||
replying: true
|
|
||||||
};
|
|
||||||
case types.MESSAGES.REPLY_CANCEL:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
replyMessage: {},
|
|
||||||
replying: false
|
|
||||||
};
|
|
||||||
case types.MESSAGES.SET_INPUT:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
message: action.message
|
|
||||||
};
|
|
||||||
case types.MESSAGES.CLEAR_INPUT:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
message: {}
|
|
||||||
};
|
|
||||||
case types.MESSAGES.TOGGLE_REACTION_PICKER:
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
showReactionPicker: !state.showReactionPicker,
|
|
||||||
actionMessage: action.message
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,7 @@ const initialState = {
|
||||||
loading: false
|
loading: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function messages(state = initialState, action) {
|
export default function(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SELECTED_USERS.ADD_USER:
|
case SELECTED_USERS.ADD_USER:
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { USERS_TYPING } from '../actions/actionsTypes';
|
||||||
|
|
||||||
|
const initialState = [];
|
||||||
|
|
||||||
|
export default function usersTyping(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case USERS_TYPING.ADD:
|
||||||
|
if (state.findIndex(item => item === action.username) === -1) {
|
||||||
|
return [...state, action.username];
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
case USERS_TYPING.REMOVE:
|
||||||
|
return state.filter(item => item !== action.username);
|
||||||
|
case USERS_TYPING.CLEAR:
|
||||||
|
return initialState;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import RNUserDefaults from 'rn-user-defaults';
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import { selectServerRequest } from '../actions/server';
|
import { selectServerRequest } from '../actions/server';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/database';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import EventEmitter from '../utils/events';
|
import EventEmitter from '../utils/events';
|
||||||
import { appStart } from '../actions';
|
import { appStart } from '../actions';
|
||||||
|
@ -66,12 +66,19 @@ const handleOpen = function* handleOpen({ params }) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// search if deep link's server already exists
|
// search if deep link's server already exists
|
||||||
const servers = yield database.databases.serversDB.objects('servers').filtered('id = $0', host); // TODO: need better test
|
const serversDB = database.servers;
|
||||||
if (servers.length && user) {
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
|
try {
|
||||||
|
const servers = yield serversCollection.find(host);
|
||||||
|
if (servers && user) {
|
||||||
yield put(selectServerRequest(host));
|
yield put(selectServerRequest(host));
|
||||||
yield take(types.SERVER.SELECT_SUCCESS);
|
yield take(types.SERVER.SELECT_SUCCESS);
|
||||||
yield navigate({ params });
|
yield navigate({ params });
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing?
|
||||||
|
}
|
||||||
// if deep link is from a different server
|
// if deep link is from a different server
|
||||||
const result = yield RocketChat.getServerInfo(server);
|
const result = yield RocketChat.getServerInfo(server);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
|
@ -81,7 +88,6 @@ const handleOpen = function* handleOpen({ params }) {
|
||||||
yield delay(1000);
|
yield delay(1000);
|
||||||
EventEmitter.emit('NewServer', { server: host });
|
EventEmitter.emit('NewServer', { server: host });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { AsyncStorage } from 'react-native';
|
||||||
import { put, takeLatest, all } from 'redux-saga/effects';
|
import { put, takeLatest, all } from 'redux-saga/effects';
|
||||||
import SplashScreen from 'react-native-splash-screen';
|
import SplashScreen from 'react-native-splash-screen';
|
||||||
import RNUserDefaults from 'rn-user-defaults';
|
import RNUserDefaults from 'rn-user-defaults';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
import { selectServerRequest } from '../actions/server';
|
import { selectServerRequest } from '../actions/server';
|
||||||
|
@ -12,11 +13,12 @@ import { APP } from '../actions/actionsTypes';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
import database from '../lib/realm';
|
|
||||||
import {
|
import {
|
||||||
SERVERS, SERVER_ICON, SERVER_NAME, SERVER_URL, TOKEN, USER_ID
|
SERVERS, SERVER_ICON, SERVER_NAME, SERVER_URL, TOKEN, USER_ID
|
||||||
} from '../constants/userDefaults';
|
} from '../constants/userDefaults';
|
||||||
import { isIOS } from '../utils/deviceInfo';
|
import { isIOS } from '../utils/deviceInfo';
|
||||||
|
import database from '../lib/database';
|
||||||
|
import protectedFunction from '../lib/methods/helpers/protectedFunction';
|
||||||
|
|
||||||
const restore = function* restore() {
|
const restore = function* restore() {
|
||||||
try {
|
try {
|
||||||
|
@ -31,34 +33,47 @@ const restore = function* restore() {
|
||||||
server: RNUserDefaults.get('currentServer')
|
server: RNUserDefaults.get('currentServer')
|
||||||
});
|
});
|
||||||
|
|
||||||
// get native credentials
|
let servers = yield RNUserDefaults.objectForKey(SERVERS);
|
||||||
if (isIOS && !hasMigration) {
|
|
||||||
const { serversDB } = database.databases;
|
|
||||||
const servers = yield RNUserDefaults.objectForKey(SERVERS);
|
|
||||||
if (servers) {
|
|
||||||
serversDB.write(() => {
|
|
||||||
servers.forEach(async(serverItem) => {
|
|
||||||
const serverInfo = {
|
|
||||||
id: serverItem[SERVER_URL],
|
|
||||||
name: serverItem[SERVER_NAME],
|
|
||||||
iconURL: serverItem[SERVER_ICON]
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
serversDB.create('servers', serverInfo, true);
|
|
||||||
await RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ serverInfo.id }`, serverItem[USER_ID]);
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
yield AsyncStorage.setItem('hasMigration', '1');
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not have current
|
// if not have current
|
||||||
if (servers && servers.length !== 0 && (!token || !server)) {
|
if (servers && servers.length !== 0 && (!token || !server)) {
|
||||||
server = servers[0][SERVER_URL];
|
server = servers[0][SERVER_URL];
|
||||||
token = servers[0][TOKEN];
|
token = servers[0][TOKEN];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get native credentials
|
||||||
|
if (servers && !hasMigration) {
|
||||||
|
// parse servers
|
||||||
|
servers = yield Promise.all(servers.map(async(s) => {
|
||||||
|
await RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ s[SERVER_URL] }`, s[USER_ID]);
|
||||||
|
return ({ id: s[SERVER_URL], name: s[SERVER_NAME], iconURL: s[SERVER_ICON] });
|
||||||
|
}));
|
||||||
|
try {
|
||||||
|
const serversDB = database.servers;
|
||||||
|
yield serversDB.action(async() => {
|
||||||
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
|
const allServerRecords = await serversCollection.query().fetch();
|
||||||
|
|
||||||
|
// filter servers
|
||||||
|
let serversToCreate = servers.filter(i1 => !allServerRecords.find(i2 => i1.id === i2.id));
|
||||||
|
|
||||||
|
// Create
|
||||||
|
serversToCreate = serversToCreate.map(record => serversCollection.prepareCreate(protectedFunction((s) => {
|
||||||
|
s._raw = sanitizedRaw({ id: record.id }, serversCollection.schema);
|
||||||
|
Object.assign(s, record);
|
||||||
|
})));
|
||||||
|
|
||||||
|
const allRecords = serversToCreate;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await serversDB.batch(...allRecords);
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
return allRecords.length;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortPreferences = yield RocketChat.getSortPreferences();
|
const sortPreferences = yield RocketChat.getSortPreferences();
|
||||||
|
@ -77,13 +92,16 @@ const restore = function* restore() {
|
||||||
]);
|
]);
|
||||||
yield put(actions.appStart('outside'));
|
yield put(actions.appStart('outside'));
|
||||||
} else if (server) {
|
} else if (server) {
|
||||||
const serverObj = database.databases.serversDB.objectForPrimaryKey('servers', server);
|
const serversDB = database.servers;
|
||||||
|
const serverCollections = serversDB.collections.get('servers');
|
||||||
|
const serverObj = yield serverCollections.find(server);
|
||||||
yield put(selectServerRequest(server, serverObj && serverObj.version));
|
yield put(selectServerRequest(server, serverObj && serverObj.version));
|
||||||
}
|
}
|
||||||
|
|
||||||
yield put(actions.appReady({}));
|
yield put(actions.appReady({}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
|
yield put(actions.appStart('outside'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
put, call, takeLatest, select, take, fork, cancel
|
put, call, takeLatest, select, take, fork, cancel
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
import RNUserDefaults from 'rn-user-defaults';
|
import RNUserDefaults from 'rn-user-defaults';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import 'moment/min/locales';
|
import 'moment/min/locales';
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ import { toMomentLocale } from '../utils/moment';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/database';
|
||||||
import EventEmitter from '../utils/events';
|
import EventEmitter from '../utils/events';
|
||||||
|
|
||||||
const getServer = state => state.server.server;
|
const getServer = state => state.server.server;
|
||||||
|
@ -77,12 +78,28 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
I18n.locale = user.language;
|
I18n.locale = user.language;
|
||||||
moment.locale(toMomentLocale(user.language));
|
moment.locale(toMomentLocale(user.language));
|
||||||
|
|
||||||
const { serversDB } = database.databases;
|
const serversDB = database.servers;
|
||||||
serversDB.write(() => {
|
const usersCollection = serversDB.collections.get('users');
|
||||||
|
const u = {
|
||||||
|
token: user.token,
|
||||||
|
username: user.username,
|
||||||
|
name: user.name,
|
||||||
|
language: user.language,
|
||||||
|
status: user.status,
|
||||||
|
roles: user.roles
|
||||||
|
};
|
||||||
|
yield serversDB.action(async() => {
|
||||||
try {
|
try {
|
||||||
serversDB.create('user', user, true);
|
const userRecord = await usersCollection.find(user.id);
|
||||||
|
await userRecord.update((record) => {
|
||||||
|
record._raw = sanitizedRaw({ id: user.id, ...record._raw }, usersCollection.schema);
|
||||||
|
Object.assign(record, u);
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
await usersCollection.create((record) => {
|
||||||
|
record._raw = sanitizedRaw({ id: user.id }, usersCollection.schema);
|
||||||
|
Object.assign(record, u);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -108,14 +125,18 @@ const handleLogout = function* handleLogout() {
|
||||||
if (server) {
|
if (server) {
|
||||||
try {
|
try {
|
||||||
yield call(logoutCall, { server });
|
yield call(logoutCall, { server });
|
||||||
const { serversDB } = database.databases;
|
const serversDB = database.servers;
|
||||||
// all servers
|
// all servers
|
||||||
const servers = yield serversDB.objects('servers');
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
|
|
||||||
// filter logging out server and delete it
|
// filter logging out server and delete it
|
||||||
const serverRecord = servers.filtered('id = $0', server);
|
yield serversDB.action(async() => {
|
||||||
serversDB.write(() => {
|
const serverRecord = await serversCollection.find(server);
|
||||||
serversDB.delete(serverRecord);
|
await serverRecord.destroyPermanently();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const servers = yield serversCollection.query().fetch();
|
||||||
|
|
||||||
// see if there's other logged in servers and selects first one
|
// see if there's other logged in servers and selects first one
|
||||||
if (servers.length > 0) {
|
if (servers.length > 0) {
|
||||||
const newServer = servers[0].id;
|
const newServer = servers[0].id;
|
||||||
|
|
|
@ -1,92 +1,37 @@
|
||||||
import {
|
import { takeLatest } from 'redux-saga/effects';
|
||||||
takeLatest, put, call, delay
|
import { Q } from '@nozbe/watermelondb';
|
||||||
} from 'redux-saga/effects';
|
|
||||||
|
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
import { MESSAGES } from '../actions/actionsTypes';
|
import { MESSAGES } from '../actions/actionsTypes';
|
||||||
import {
|
|
||||||
deleteSuccess,
|
|
||||||
deleteFailure,
|
|
||||||
editSuccess,
|
|
||||||
editFailure,
|
|
||||||
toggleStarSuccess,
|
|
||||||
toggleStarFailure,
|
|
||||||
togglePinSuccess,
|
|
||||||
togglePinFailure,
|
|
||||||
replyInit
|
|
||||||
} from '../actions/messages';
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/database';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
|
|
||||||
const deleteMessage = message => RocketChat.deleteMessage(message);
|
const goRoom = function goRoom({ rid, name, message }) {
|
||||||
const editMessage = message => RocketChat.editMessage(message);
|
|
||||||
const toggleStarMessage = message => RocketChat.toggleStarMessage(message);
|
|
||||||
const togglePinMessage = message => RocketChat.togglePinMessage(message);
|
|
||||||
|
|
||||||
const handleDeleteRequest = function* handleDeleteRequest({ message }) {
|
|
||||||
try {
|
|
||||||
yield call(deleteMessage, message);
|
|
||||||
yield put(deleteSuccess());
|
|
||||||
} catch (error) {
|
|
||||||
yield put(deleteFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditRequest = function* handleEditRequest({ message }) {
|
|
||||||
try {
|
|
||||||
yield call(editMessage, message);
|
|
||||||
yield put(editSuccess());
|
|
||||||
} catch (error) {
|
|
||||||
yield put(editFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleStarRequest = function* handleToggleStarRequest({ message }) {
|
|
||||||
try {
|
|
||||||
yield call(toggleStarMessage, message);
|
|
||||||
yield put(toggleStarSuccess());
|
|
||||||
} catch (error) {
|
|
||||||
yield put(toggleStarFailure());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTogglePinRequest = function* handleTogglePinRequest({ message }) {
|
|
||||||
try {
|
|
||||||
yield call(togglePinMessage, message);
|
|
||||||
yield put(togglePinSuccess());
|
|
||||||
} catch (error) {
|
|
||||||
yield put(togglePinFailure(error));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const goRoom = function goRoom({ rid, name }) {
|
|
||||||
Navigation.navigate('RoomsListView');
|
Navigation.navigate('RoomsListView');
|
||||||
Navigation.navigate('RoomView', { rid, name, t: 'd' });
|
Navigation.navigate('RoomView', {
|
||||||
|
rid, name, t: 'd', message
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {
|
const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {
|
||||||
try {
|
try {
|
||||||
|
const db = database.active;
|
||||||
const { username } = message.u;
|
const { username } = message.u;
|
||||||
const subscriptions = database.objects('subscriptions').filtered('name = $0', username);
|
const subsCollection = db.collections.get('subscriptions');
|
||||||
|
const subscriptions = yield subsCollection.query(Q.where('name', username)).fetch();
|
||||||
if (subscriptions.length) {
|
if (subscriptions.length) {
|
||||||
yield goRoom({ rid: subscriptions[0].rid, name: username });
|
yield goRoom({ rid: subscriptions[0].rid, name: username, message });
|
||||||
} else {
|
} else {
|
||||||
const room = yield RocketChat.createDirectMessage(username);
|
const room = yield RocketChat.createDirectMessage(username);
|
||||||
yield goRoom({ rid: room.rid, name: username });
|
yield goRoom({ rid: room.rid, name: username, message });
|
||||||
}
|
}
|
||||||
yield delay(500);
|
|
||||||
yield put(replyInit(message, false));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(MESSAGES.DELETE_REQUEST, handleDeleteRequest);
|
|
||||||
yield takeLatest(MESSAGES.EDIT_REQUEST, handleEditRequest);
|
|
||||||
yield takeLatest(MESSAGES.TOGGLE_STAR_REQUEST, handleToggleStarRequest);
|
|
||||||
yield takeLatest(MESSAGES.TOGGLE_PIN_REQUEST, handleTogglePinRequest);
|
|
||||||
yield takeLatest(MESSAGES.REPLY_BROADCAST, handleReplyBroadcast);
|
yield takeLatest(MESSAGES.REPLY_BROADCAST, handleReplyBroadcast);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Alert } from 'react-native';
|
import { Alert } from 'react-native';
|
||||||
import {
|
import {
|
||||||
call, takeLatest, take, select, delay
|
takeLatest, take, select, delay
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
|
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
|
@ -19,7 +19,7 @@ const watchUserTyping = function* watchUserTyping({ rid, status }) {
|
||||||
yield RocketChat.emitTyping(rid, status);
|
yield RocketChat.emitTyping(rid, status);
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
yield call(delay, 5000);
|
yield delay(5000);
|
||||||
yield RocketChat.emitTyping(rid, false);
|
yield RocketChat.emitTyping(rid, false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -2,36 +2,67 @@ import {
|
||||||
put, select, race, take, fork, cancel, delay
|
put, select, race, take, fork, cancel, delay
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
import { BACKGROUND, INACTIVE } from 'redux-enhancer-react-native-appstate';
|
import { BACKGROUND, INACTIVE } from 'redux-enhancer-react-native-appstate';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import { roomsSuccess, roomsFailure } from '../actions/rooms';
|
import { roomsSuccess, roomsFailure } from '../actions/rooms';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/database';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms';
|
import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
|
||||||
const handleRoomsRequest = function* handleRoomsRequest() {
|
const handleRoomsRequest = function* handleRoomsRequest() {
|
||||||
try {
|
try {
|
||||||
|
const serversDB = database.servers;
|
||||||
yield RocketChat.subscribeRooms();
|
yield RocketChat.subscribeRooms();
|
||||||
const newRoomsUpdatedAt = new Date();
|
const newRoomsUpdatedAt = new Date();
|
||||||
const server = yield select(state => state.server.server);
|
const server = yield select(state => state.server.server);
|
||||||
const [serverRecord] = database.databases.serversDB.objects('servers').filtered('id = $0', server);
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
|
const serverRecord = yield serversCollection.find(server);
|
||||||
const { roomsUpdatedAt } = serverRecord;
|
const { roomsUpdatedAt } = serverRecord;
|
||||||
const [subscriptionsResult, roomsResult] = yield RocketChat.getRooms(roomsUpdatedAt);
|
const [subscriptionsResult, roomsResult] = yield RocketChat.getRooms(roomsUpdatedAt);
|
||||||
const { subscriptions } = mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
|
const { subscriptions } = mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
|
||||||
|
|
||||||
database.write(() => {
|
const db = database.active;
|
||||||
subscriptions.forEach((subscription) => {
|
yield db.action(async() => {
|
||||||
|
const subCollection = db.collections.get('subscriptions');
|
||||||
|
if (!subscriptions.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subsIds = subscriptions.map(sub => sub.rid);
|
||||||
|
const existingSubs = await subCollection.query(Q.where('id', Q.oneOf(subsIds))).fetch();
|
||||||
|
const subsToUpdate = existingSubs.filter(i1 => subscriptions.find(i2 => i1._id === i2._id));
|
||||||
|
const subsToCreate = subscriptions.filter(i1 => !existingSubs.find(i2 => i1._id === i2._id));
|
||||||
|
// TODO: subsToDelete?
|
||||||
|
|
||||||
|
const allRecords = [
|
||||||
|
...subsToCreate.map(subscription => subCollection.prepareCreate((s) => {
|
||||||
|
s._raw = sanitizedRaw({ id: subscription.rid }, subCollection.schema);
|
||||||
|
return Object.assign(s, subscription);
|
||||||
|
})),
|
||||||
|
...subsToUpdate.map((subscription) => {
|
||||||
|
const newSub = subscriptions.find(s => s._id === subscription._id);
|
||||||
|
return subscription.prepareUpdate(() => {
|
||||||
|
Object.assign(subscription, newSub);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
database.create('subscriptions', subscription, true);
|
await db.batch(...allRecords);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
return allRecords.length;
|
||||||
});
|
});
|
||||||
});
|
|
||||||
database.databases.serversDB.write(() => {
|
yield serversDB.action(async() => {
|
||||||
try {
|
try {
|
||||||
database.databases.serversDB.create('servers', { id: server, roomsUpdatedAt: newRoomsUpdatedAt }, true);
|
await serverRecord.update((record) => {
|
||||||
|
record.roomsUpdatedAt = newRoomsUpdatedAt;
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
import { Alert } from 'react-native';
|
import { Alert } from 'react-native';
|
||||||
import RNUserDefaults from 'rn-user-defaults';
|
import RNUserDefaults from 'rn-user-defaults';
|
||||||
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
|
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
import { SERVER } from '../actions/actionsTypes';
|
import { SERVER } from '../actions/actionsTypes';
|
||||||
|
@ -12,7 +13,7 @@ import {
|
||||||
} from '../actions/server';
|
} from '../actions/server';
|
||||||
import { setUser } from '../actions/login';
|
import { setUser } from '../actions/login';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/realm';
|
import database from '../lib/database';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import { extractHostname } from '../utils/server';
|
import { extractHostname } from '../utils/server';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
@ -29,8 +30,20 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
database.databases.serversDB.write(() => {
|
const serversDB = database.servers;
|
||||||
database.databases.serversDB.create('servers', { id: server, version: serverInfo.version }, true);
|
const serversCollection = serversDB.collections.get('servers');
|
||||||
|
yield serversDB.action(async() => {
|
||||||
|
try {
|
||||||
|
const serverRecord = await serversCollection.find(server);
|
||||||
|
await serverRecord.update((record) => {
|
||||||
|
record.version = serverInfo.version;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
await serversCollection.create((record) => {
|
||||||
|
record._raw = sanitizedRaw({ id: server }, serversCollection.schema);
|
||||||
|
record.version = serverInfo.version;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return serverInfo;
|
return serverInfo;
|
||||||
|
@ -41,11 +54,27 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||||
|
|
||||||
const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }) {
|
const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }) {
|
||||||
try {
|
try {
|
||||||
const { serversDB } = database.databases;
|
const serversDB = database.servers;
|
||||||
|
|
||||||
yield RNUserDefaults.set('currentServer', server);
|
yield RNUserDefaults.set('currentServer', server);
|
||||||
const userId = yield RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
|
const userId = yield RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
|
||||||
const user = userId && serversDB.objectForPrimaryKey('user', userId);
|
const userCollections = serversDB.collections.get('users');
|
||||||
|
let user = null;
|
||||||
|
if (userId) {
|
||||||
|
try {
|
||||||
|
user = yield userCollections.find(userId);
|
||||||
|
user = {
|
||||||
|
token: user.token,
|
||||||
|
username: user.username,
|
||||||
|
name: user.name,
|
||||||
|
language: user.language,
|
||||||
|
status: user.status,
|
||||||
|
roles: user.roles
|
||||||
|
};
|
||||||
|
user = { ...user, roles: JSON.parse(user.roles) };
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const servers = yield RNUserDefaults.objectForKey(SERVERS);
|
const servers = yield RNUserDefaults.objectForKey(SERVERS);
|
||||||
const userCredentials = servers && servers.find(srv => srv[SERVER_URL] === server);
|
const userCredentials = servers && servers.find(srv => srv[SERVER_URL] === server);
|
||||||
|
@ -62,9 +91,20 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
||||||
yield put(actions.appStart('outside'));
|
yield put(actions.appStart('outside'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = database.objects('settings');
|
const db = database.active;
|
||||||
|
const serversCollection = db.collections.get('settings');
|
||||||
|
const settingsRecords = yield serversCollection.query().fetch();
|
||||||
|
const settings = Object.values(settingsRecords).map(item => ({
|
||||||
|
_id: item.id,
|
||||||
|
valueAsString: item.valueAsString,
|
||||||
|
valueAsBoolean: item.valueAsBoolean,
|
||||||
|
valueAsNumber: item.valueAsNumber,
|
||||||
|
_updatedAt: item._updatedAt
|
||||||
|
}));
|
||||||
yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length))));
|
yield put(actions.setAllSettings(RocketChat.parseSettings(settings.slice(0, settings.length))));
|
||||||
|
|
||||||
|
yield RocketChat.setCustomEmojis();
|
||||||
|
|
||||||
let serverInfo;
|
let serverInfo;
|
||||||
if (fetchVersion) {
|
if (fetchVersion) {
|
||||||
serverInfo = yield getServerInfo({ server, raiseError: false });
|
serverInfo = yield getServerInfo({ server, raiseError: false });
|
||||||
|
|
|
@ -9,7 +9,7 @@ export const loggerConfig = bugsnag.config;
|
||||||
export const { leaveBreadcrumb } = bugsnag;
|
export const { leaveBreadcrumb } = bugsnag;
|
||||||
|
|
||||||
export default (e) => {
|
export default (e) => {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error && !__DEV__) {
|
||||||
bugsnag.notify(e);
|
bugsnag.notify(e);
|
||||||
} else {
|
} else {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
SWITCH_TRACK_COLOR, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE, COLOR_SEPARATOR
|
SWITCH_TRACK_COLOR, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE, COLOR_SEPARATOR
|
||||||
} from '../../constants/colors';
|
} from '../../constants/colors';
|
||||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
import database from '../../lib/realm';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
contentContainerStyle: {
|
contentContainerStyle: {
|
||||||
|
@ -49,11 +48,19 @@ export default class AutoTranslateView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.rid = props.navigation.getParam('rid');
|
this.rid = props.navigation.getParam('rid');
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
const room = props.navigation.getParam('room');
|
||||||
|
|
||||||
|
if (room && room.observe) {
|
||||||
|
this.roomObservable = room.observe();
|
||||||
|
this.subscription = this.roomObservable
|
||||||
|
.subscribe((changes) => {
|
||||||
|
this.room = changes;
|
||||||
|
});
|
||||||
|
}
|
||||||
this.state = {
|
this.state = {
|
||||||
languages: [],
|
languages: [],
|
||||||
selectedLanguage: this.rooms[0].autoTranslateLanguage,
|
selectedLanguage: room.autoTranslateLanguage,
|
||||||
enableAutoTranslate: this.rooms[0].autoTranslate
|
enableAutoTranslate: room.autoTranslate
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +73,12 @@ export default class AutoTranslateView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.subscription && this.subscription.unsubscribe) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
toggleAutoTranslate = async() => {
|
toggleAutoTranslate = async() => {
|
||||||
const { enableAutoTranslate } = this.state;
|
const { enableAutoTranslate } = this.state;
|
||||||
try {
|
try {
|
||||||
|
@ -152,5 +165,3 @@ export default class AutoTranslateView extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.disableYellowBox = true;
|
|
||||||
|
|
|
@ -26,7 +26,8 @@ class MessagesView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
navigation: PropTypes.object
|
navigation: PropTypes.object,
|
||||||
|
customEmojis: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -80,7 +81,8 @@ class MessagesView extends React.Component {
|
||||||
isEdited: !!item.editedAt,
|
isEdited: !!item.editedAt,
|
||||||
isHeader: true,
|
isHeader: true,
|
||||||
attachments: item.attachments || [],
|
attachments: item.attachments || [],
|
||||||
onOpenFileModal: this.onOpenFileModal
|
onOpenFileModal: this.onOpenFileModal,
|
||||||
|
getCustomEmoji: this.getCustomEmoji
|
||||||
});
|
});
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
|
@ -145,7 +147,7 @@ class MessagesView extends React.Component {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
actionTitle: I18n.t('Unstar'),
|
actionTitle: I18n.t('Unstar'),
|
||||||
handleActionPress: message => RocketChat.toggleStarMessage(message)
|
handleActionPress: message => RocketChat.toggleStarMessage(message._id, message.starred)
|
||||||
},
|
},
|
||||||
// Pinned Messages Screen
|
// Pinned Messages Screen
|
||||||
Pinned: {
|
Pinned: {
|
||||||
|
@ -161,7 +163,7 @@ class MessagesView extends React.Component {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
actionTitle: I18n.t('Unpin'),
|
actionTitle: I18n.t('Unpin'),
|
||||||
handleActionPress: message => RocketChat.togglePinMessage(message)
|
handleActionPress: message => RocketChat.togglePinMessage(message._id, message.pinned)
|
||||||
}
|
}
|
||||||
}[name]);
|
}[name]);
|
||||||
}
|
}
|
||||||
|
@ -191,6 +193,15 @@ class MessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCustomEmoji = (name) => {
|
||||||
|
const { customEmojis } = this.props;
|
||||||
|
const emoji = customEmojis[name];
|
||||||
|
if (emoji) {
|
||||||
|
return emoji;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
onOpenFileModal = (attachment) => {
|
onOpenFileModal = (attachment) => {
|
||||||
this.setState({ selectedAttachment: attachment, photoModalVisible: true });
|
this.setState({ selectedAttachment: attachment, photoModalVisible: true });
|
||||||
}
|
}
|
||||||
|
@ -285,7 +296,8 @@ const mapStateToProps = state => ({
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
}
|
},
|
||||||
|
customEmojis: state.customEmojis
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(MessagesView);
|
export default connect(mapStateToProps)(MessagesView);
|
||||||
|
|
|
@ -6,13 +6,15 @@ import {
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
|
import { orderBy } from 'lodash';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
import database, { safeAddListener } from '../lib/realm';
|
import database from '../lib/database';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import UserItem from '../presentation/UserItem';
|
import UserItem from '../presentation/UserItem';
|
||||||
import debounce from '../utils/debounce';
|
|
||||||
import sharedStyles from './Styles';
|
import sharedStyles from './Styles';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
import log from '../utils/log';
|
||||||
import Touch from '../utils/touch';
|
import Touch from '../utils/touch';
|
||||||
import { isIOS } from '../utils/deviceInfo';
|
import { isIOS } from '../utils/deviceInfo';
|
||||||
import SearchBox from '../containers/SearchBox';
|
import SearchBox from '../containers/SearchBox';
|
||||||
|
@ -67,24 +69,46 @@ class NewMessageView extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.data = database.objects('subscriptions').filtered('t = $0', 'd').sorted('roomUpdatedAt', true);
|
this.init();
|
||||||
this.state = {
|
this.state = {
|
||||||
search: []
|
search: [],
|
||||||
|
chats: []
|
||||||
};
|
};
|
||||||
safeAddListener(this.data, this.updateState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const { search } = this.state;
|
const { search, chats } = this.state;
|
||||||
if (!equal(nextState.search, search)) {
|
if (!equal(nextState.search, search)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!equal(nextState.chats, chats)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.updateState.stop();
|
if (this.querySubscription && this.querySubscription.unsubscribe) {
|
||||||
this.data.removeAllListeners();
|
this.querySubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
|
init = async() => {
|
||||||
|
try {
|
||||||
|
const db = database.active;
|
||||||
|
const observable = await db.collections
|
||||||
|
.get('subscriptions')
|
||||||
|
.query(Q.where('t', 'd'))
|
||||||
|
.observeWithColumns(['room_updated_at']);
|
||||||
|
|
||||||
|
this.querySubscription = observable.subscribe((data) => {
|
||||||
|
const chats = orderBy(data, ['roomUpdatedAt'], ['desc']);
|
||||||
|
this.setState({ chats });
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchChangeText(text) {
|
onSearchChangeText(text) {
|
||||||
|
@ -102,11 +126,6 @@ class NewMessageView extends React.Component {
|
||||||
return navigation.pop();
|
return navigation.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
|
||||||
updateState = debounce(() => {
|
|
||||||
this.forceUpdate();
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
search = async(text) => {
|
search = async(text) => {
|
||||||
const result = await RocketChat.search({ text, filterRooms: false });
|
const result = await RocketChat.search({ text, filterRooms: false });
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -134,7 +153,7 @@ class NewMessageView extends React.Component {
|
||||||
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />;
|
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />;
|
||||||
|
|
||||||
renderItem = ({ item, index }) => {
|
renderItem = ({ item, index }) => {
|
||||||
const { search } = this.state;
|
const { search, chats } = this.state;
|
||||||
const { baseUrl, user } = this.props;
|
const { baseUrl, user } = this.props;
|
||||||
|
|
||||||
let style = {};
|
let style = {};
|
||||||
|
@ -144,7 +163,7 @@ class NewMessageView extends React.Component {
|
||||||
if (search.length > 0 && index === search.length - 1) {
|
if (search.length > 0 && index === search.length - 1) {
|
||||||
style = { ...style, ...sharedStyles.separatorBottom };
|
style = { ...style, ...sharedStyles.separatorBottom };
|
||||||
}
|
}
|
||||||
if (search.length === 0 && index === this.data.length - 1) {
|
if (search.length === 0 && index === chats.length - 1) {
|
||||||
style = { ...style, ...sharedStyles.separatorBottom };
|
style = { ...style, ...sharedStyles.separatorBottom };
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -161,10 +180,10 @@ class NewMessageView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList = () => {
|
renderList = () => {
|
||||||
const { search } = this.state;
|
const { search, chats } = this.state;
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={search.length > 0 ? search : this.data}
|
data={search.length > 0 ? search : chats}
|
||||||
extraData={this.state}
|
extraData={this.state}
|
||||||
keyExtractor={item => item._id}
|
keyExtractor={item => item._id}
|
||||||
ListHeaderComponent={this.renderHeader}
|
ListHeaderComponent={this.renderHeader}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import I18n from '../../i18n';
|
||||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
import database from '../../lib/realm';
|
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
|
||||||
|
@ -112,13 +111,37 @@ export default class NotificationPreferencesView extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.mounted = false;
|
||||||
this.rid = props.navigation.getParam('rid');
|
this.rid = props.navigation.getParam('rid');
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
const room = props.navigation.getParam('room');
|
||||||
|
|
||||||
|
if (room && room.observe) {
|
||||||
|
this.roomObservable = room.observe();
|
||||||
|
this.subscription = this.roomObservable
|
||||||
|
.subscribe((changes) => {
|
||||||
|
if (this.mounted) {
|
||||||
|
this.setState({ room: changes });
|
||||||
|
} else {
|
||||||
|
this.state.room = changes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
room: JSON.parse(JSON.stringify(this.rooms[0] || {}))
|
room: room || {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.subscription && this.subscription.unsubscribe) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onValueChangeSwitch = async(key, value) => {
|
onValueChangeSwitch = async(key, value) => {
|
||||||
const { room: newRoom } = this.state;
|
const { room: newRoom } = this.state;
|
||||||
newRoom[key] = value;
|
newRoom[key] = value;
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
import equal from 'deep-equal';
|
|
||||||
|
|
||||||
import { leaveRoom as leaveRoomAction } from '../../actions/room';
|
import { leaveRoom as leaveRoomAction } from '../../actions/room';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -13,7 +12,6 @@ import sharedStyles from '../Styles';
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import Status from '../../containers/Status';
|
import Status from '../../containers/Status';
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||||
|
@ -43,22 +41,38 @@ class RoomActionsView extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.mounted = false;
|
||||||
|
const room = props.navigation.getParam('room');
|
||||||
|
|
||||||
|
if (room && room.observe) {
|
||||||
|
this.roomObservable = room.observe();
|
||||||
|
this.subscription = this.roomObservable
|
||||||
|
.subscribe((changes) => {
|
||||||
|
if (this.mounted) {
|
||||||
|
this.setState({ room: changes });
|
||||||
|
} else {
|
||||||
|
this.state.room = changes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.rid = props.navigation.getParam('rid');
|
this.rid = props.navigation.getParam('rid');
|
||||||
this.t = props.navigation.getParam('t');
|
this.t = props.navigation.getParam('t');
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
|
||||||
this.state = {
|
this.state = {
|
||||||
room: this.rooms[0] || { rid: this.rid, t: this.t },
|
room: room || { rid: this.rid, t: this.t },
|
||||||
membersCount: 0,
|
membersCount: 0,
|
||||||
member: {},
|
member: {},
|
||||||
joined: this.rooms.length > 0,
|
joined: !!room,
|
||||||
canViewMembers: false,
|
canViewMembers: false,
|
||||||
canAutoTranslate: false
|
canAutoTranslate: false,
|
||||||
|
canAddUser: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
if (!room._id) {
|
if (!room.id) {
|
||||||
try {
|
try {
|
||||||
const result = await RocketChat.getChannelInfo(room.rid);
|
const result = await RocketChat.getChannelInfo(room.rid);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
@ -69,7 +83,7 @@ class RoomActionsView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (room && room.t !== 'd' && this.canViewMembers) {
|
if (room && room.t !== 'd' && this.canViewMembers()) {
|
||||||
try {
|
try {
|
||||||
const counters = await RocketChat.getRoomCounters(room.rid, room.t);
|
const counters = await RocketChat.getRoomCounters(room.rid, room.t);
|
||||||
if (counters.success) {
|
if (counters.success) {
|
||||||
|
@ -82,36 +96,16 @@ class RoomActionsView extends React.Component {
|
||||||
this.updateRoomMember();
|
this.updateRoomMember();
|
||||||
}
|
}
|
||||||
|
|
||||||
const canAutoTranslate = RocketChat.canAutoTranslate();
|
const canAutoTranslate = await RocketChat.canAutoTranslate();
|
||||||
this.setState({ canAutoTranslate });
|
this.setState({ canAutoTranslate });
|
||||||
|
|
||||||
safeAddListener(this.rooms, this.updateRoom);
|
this.canAddUser();
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
const {
|
|
||||||
room, membersCount, member, joined, canViewMembers
|
|
||||||
} = this.state;
|
|
||||||
if (nextState.membersCount !== membersCount) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextState.joined !== joined) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextState.canViewMembers !== canViewMembers) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.room, room)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.member, member)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.rooms.removeAllListeners();
|
if (this.subscription && this.subscription.unsubscribe) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressTouchable = (item) => {
|
onPressTouchable = (item) => {
|
||||||
|
@ -125,32 +119,37 @@ class RoomActionsView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to componentDidMount
|
// TODO: move to componentDidMount
|
||||||
get canAddUser() {
|
// eslint-disable-next-line react/sort-comp
|
||||||
|
canAddUser = async() => {
|
||||||
const { room, joined } = this.state;
|
const { room, joined } = this.state;
|
||||||
const { rid, t } = room;
|
const { rid, t } = room;
|
||||||
|
let canAdd = false;
|
||||||
|
|
||||||
const userInRoom = joined;
|
const userInRoom = joined;
|
||||||
const permissions = RocketChat.hasPermission(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room'], rid);
|
const permissions = await RocketChat.hasPermission(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room'], rid);
|
||||||
|
|
||||||
|
if (permissions) {
|
||||||
if (userInRoom && permissions['add-user-to-joined-room']) {
|
if (userInRoom && permissions['add-user-to-joined-room']) {
|
||||||
return true;
|
canAdd = true;
|
||||||
}
|
}
|
||||||
if (t === 'c' && permissions['add-user-to-any-c-room']) {
|
if (t === 'c' && permissions['add-user-to-any-c-room']) {
|
||||||
return true;
|
canAdd = true;
|
||||||
}
|
}
|
||||||
if (t === 'p' && permissions['add-user-to-any-p-room']) {
|
if (t === 'p' && permissions['add-user-to-any-p-room']) {
|
||||||
return true;
|
canAdd = true;
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
|
this.setState({ canAddUser: canAdd });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to componentDidMount
|
// TODO: move to componentDidMount
|
||||||
get canViewMembers() {
|
// eslint-disable-next-line react/sort-comp
|
||||||
|
canViewMembers = async() => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { rid, t, broadcast } = room;
|
const { rid, t, broadcast } = room;
|
||||||
if (broadcast) {
|
if (broadcast) {
|
||||||
const viewBroadcastMemberListPermission = 'view-broadcast-member-list';
|
const viewBroadcastMemberListPermission = 'view-broadcast-member-list';
|
||||||
const permissions = RocketChat.hasPermission([viewBroadcastMemberListPermission], rid);
|
const permissions = await RocketChat.hasPermission([viewBroadcastMemberListPermission], rid);
|
||||||
if (!permissions[viewBroadcastMemberListPermission]) {
|
if (!permissions[viewBroadcastMemberListPermission]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -165,7 +164,7 @@ class RoomActionsView extends React.Component {
|
||||||
|
|
||||||
get sections() {
|
get sections() {
|
||||||
const {
|
const {
|
||||||
room, membersCount, canViewMembers, joined, canAutoTranslate
|
room, membersCount, canViewMembers, canAddUser, joined, canAutoTranslate
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
rid, t, blocker
|
rid, t, blocker
|
||||||
|
@ -175,7 +174,7 @@ class RoomActionsView extends React.Component {
|
||||||
icon: 'bell',
|
icon: 'bell',
|
||||||
name: I18n.t('Notifications'),
|
name: I18n.t('Notifications'),
|
||||||
route: 'NotificationPrefView',
|
route: 'NotificationPrefView',
|
||||||
params: { rid },
|
params: { rid, room },
|
||||||
testID: 'room-actions-notifications'
|
testID: 'room-actions-notifications'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -185,7 +184,7 @@ class RoomActionsView extends React.Component {
|
||||||
name: I18n.t('Room_Info'),
|
name: I18n.t('Room_Info'),
|
||||||
route: 'RoomInfoView',
|
route: 'RoomInfoView',
|
||||||
// forward room only if room isn't joined
|
// forward room only if room isn't joined
|
||||||
params: { rid, t },
|
params: { rid, t, room },
|
||||||
testID: 'room-actions-info'
|
testID: 'room-actions-info'
|
||||||
}],
|
}],
|
||||||
renderItem: this.renderRoomInfo
|
renderItem: this.renderRoomInfo
|
||||||
|
@ -257,7 +256,7 @@ class RoomActionsView extends React.Component {
|
||||||
icon: 'language',
|
icon: 'language',
|
||||||
name: I18n.t('Auto_Translate'),
|
name: I18n.t('Auto_Translate'),
|
||||||
route: 'AutoTranslateView',
|
route: 'AutoTranslateView',
|
||||||
params: { rid },
|
params: { rid, room },
|
||||||
testID: 'room-actions-auto-translate'
|
testID: 'room-actions-auto-translate'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -285,12 +284,12 @@ class RoomActionsView extends React.Component {
|
||||||
name: I18n.t('Members'),
|
name: I18n.t('Members'),
|
||||||
description: membersCount > 0 ? `${ membersCount } ${ I18n.t('members') }` : null,
|
description: membersCount > 0 ? `${ membersCount } ${ I18n.t('members') }` : null,
|
||||||
route: 'RoomMembersView',
|
route: 'RoomMembersView',
|
||||||
params: { rid },
|
params: { rid, room },
|
||||||
testID: 'room-actions-members'
|
testID: 'room-actions-members'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.canAddUser) {
|
if (canAddUser) {
|
||||||
actions.push({
|
actions.push({
|
||||||
icon: 'user-plus',
|
icon: 'user-plus',
|
||||||
name: I18n.t('Add_user'),
|
name: I18n.t('Add_user'),
|
||||||
|
@ -324,12 +323,6 @@ class RoomActionsView extends React.Component {
|
||||||
return sections;
|
return sections;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRoom = () => {
|
|
||||||
if (this.rooms.length > 0) {
|
|
||||||
this.setState({ room: JSON.parse(JSON.stringify(this.rooms[0])) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRoomMember = async() => {
|
updateRoomMember = async() => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { rid } = room;
|
const { rid } = room;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { connect } from 'react-redux';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
|
|
||||||
|
import database from '../../lib/database';
|
||||||
import { eraseRoom as eraseRoomAction } from '../../actions/room';
|
import { eraseRoom as eraseRoomAction } from '../../actions/room';
|
||||||
import KeyboardView from '../../presentation/KeyboardView';
|
import KeyboardView from '../../presentation/KeyboardView';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
|
@ -15,7 +16,6 @@ import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
import { showErrorAlert } from '../../utils/info';
|
import { showErrorAlert } from '../../utils/info';
|
||||||
import { LISTENER } from '../../containers/Toast';
|
import { LISTENER } from '../../containers/Toast';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import RCTextInput from '../../containers/TextInput';
|
import RCTextInput from '../../containers/TextInput';
|
||||||
import Loading from '../../containers/Loading';
|
import Loading from '../../containers/Loading';
|
||||||
|
@ -52,11 +52,9 @@ class RoomInfoEditView extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const rid = props.navigation.getParam('rid');
|
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
|
|
||||||
this.permissions = {};
|
|
||||||
this.state = {
|
this.state = {
|
||||||
room: JSON.parse(JSON.stringify(this.rooms[0] || {})),
|
room: {},
|
||||||
|
permissions: {},
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
topic: '',
|
topic: '',
|
||||||
|
@ -66,27 +64,16 @@ class RoomInfoEditView extends React.Component {
|
||||||
saving: false,
|
saving: false,
|
||||||
t: false,
|
t: false,
|
||||||
ro: false,
|
ro: false,
|
||||||
reactWhenReadOnly: false
|
reactWhenReadOnly: false,
|
||||||
|
archived: false
|
||||||
};
|
};
|
||||||
}
|
this.loadRoom();
|
||||||
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.updateRoom();
|
|
||||||
this.init();
|
|
||||||
safeAddListener(this.rooms, this.updateRoom);
|
|
||||||
const { room } = this.state;
|
|
||||||
this.permissions = RocketChat.hasPermission(PERMISSIONS_ARRAY, room.rid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const { room } = this.state;
|
|
||||||
if (!equal(nextState, this.state)) {
|
if (!equal(nextState, this.state)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!equal(nextState.room, room)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextProps, this.props)) {
|
if (!equal(nextProps, this.props)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -94,21 +81,43 @@ class RoomInfoEditView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.rooms.removeAllListeners();
|
if (this.querySubscription && this.querySubscription.unsubscribe) {
|
||||||
|
this.querySubscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRoom = () => {
|
// eslint-disable-next-line react/sort-comp
|
||||||
this.setState({ room: JSON.parse(JSON.stringify(this.rooms[0] || {})) });
|
loadRoom = async() => {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
const rid = navigation.getParam('rid', null);
|
||||||
|
if (!rid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const db = database.active;
|
||||||
|
const sub = await db.collections.get('subscriptions').find(rid);
|
||||||
|
const observable = sub.observe();
|
||||||
|
|
||||||
|
this.querySubscription = observable.subscribe((data) => {
|
||||||
|
this.room = data;
|
||||||
|
this.init(this.room);
|
||||||
|
});
|
||||||
|
|
||||||
|
const permissions = await RocketChat.hasPermission(PERMISSIONS_ARRAY, rid);
|
||||||
|
this.setState({ permissions });
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init = () => {
|
init = (room) => {
|
||||||
const { room } = this.state;
|
|
||||||
const {
|
const {
|
||||||
name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired
|
name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired
|
||||||
} = room;
|
} = room;
|
||||||
// fake password just to user knows about it
|
// fake password just to user knows about it
|
||||||
this.randomValue = random(15);
|
this.randomValue = random(15);
|
||||||
this.setState({
|
this.setState({
|
||||||
|
room,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
topic,
|
topic,
|
||||||
|
@ -116,7 +125,8 @@ class RoomInfoEditView extends React.Component {
|
||||||
t: t === 'p',
|
t: t === 'p',
|
||||||
ro,
|
ro,
|
||||||
reactWhenReadOnly,
|
reactWhenReadOnly,
|
||||||
joinCode: joinCodeRequired ? this.randomValue : ''
|
joinCode: joinCodeRequired ? this.randomValue : '',
|
||||||
|
archived: room.archived
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +138,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
|
|
||||||
reset = () => {
|
reset = () => {
|
||||||
this.clearErrors();
|
this.clearErrors();
|
||||||
this.init();
|
this.init(this.room);
|
||||||
}
|
}
|
||||||
|
|
||||||
formIsChanged = () => {
|
formIsChanged = () => {
|
||||||
|
@ -271,19 +281,20 @@ class RoomInfoEditView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
hasDeletePermission = () => {
|
hasDeletePermission = () => {
|
||||||
const { room } = this.state;
|
const { room, permissions } = this.state;
|
||||||
return (
|
return (
|
||||||
room.t === 'p' ? this.permissions[PERMISSION_DELETE_P] : this.permissions[PERMISSION_DELETE_C]
|
room.t === 'p' ? permissions[PERMISSION_DELETE_P] : permissions[PERMISSION_DELETE_C]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasArchivePermission = () => (
|
hasArchivePermission = () => {
|
||||||
this.permissions[PERMISSION_ARCHIVE] || this.permissions[PERMISSION_UNARCHIVE]
|
const { permissions } = this.state;
|
||||||
);
|
return (permissions[PERMISSION_ARCHIVE] || permissions[PERMISSION_UNARCHIVE]);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving
|
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived
|
||||||
} = this.state;
|
} = this.state;
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
|
@ -355,7 +366,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
rightLabelPrimary={I18n.t('Read_Only')}
|
rightLabelPrimary={I18n.t('Read_Only')}
|
||||||
rightLabelSecondary={I18n.t('Only_authorized_users_can_write_new_messages')}
|
rightLabelSecondary={I18n.t('Only_authorized_users_can_write_new_messages')}
|
||||||
onValueChange={value => this.setState({ ro: value })}
|
onValueChange={value => this.setState({ ro: value })}
|
||||||
disabled={!this.permissions[PERMISSION_SET_READONLY] || room.broadcast}
|
disabled={!permissions[PERMISSION_SET_READONLY] || room.broadcast}
|
||||||
testID='room-info-edit-view-ro'
|
testID='room-info-edit-view-ro'
|
||||||
/>
|
/>
|
||||||
{ro && !room.broadcast
|
{ro && !room.broadcast
|
||||||
|
@ -367,7 +378,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
rightLabelPrimary={I18n.t('Allow_Reactions')}
|
rightLabelPrimary={I18n.t('Allow_Reactions')}
|
||||||
rightLabelSecondary={I18n.t('Reactions_are_enabled')}
|
rightLabelSecondary={I18n.t('Reactions_are_enabled')}
|
||||||
onValueChange={value => this.setState({ reactWhenReadOnly: value })}
|
onValueChange={value => this.setState({ reactWhenReadOnly: value })}
|
||||||
disabled={!this.permissions[PERMISSION_SET_REACT_WHEN_READONLY]}
|
disabled={!permissions[PERMISSION_SET_REACT_WHEN_READONLY]}
|
||||||
testID='room-info-edit-view-react-when-ro'
|
testID='room-info-edit-view-react-when-ro'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -408,7 +419,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
testID='room-info-edit-view-archive'
|
testID='room-info-edit-view-archive'
|
||||||
>
|
>
|
||||||
<Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'>
|
<Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'>
|
||||||
{ room.archived ? I18n.t('UNARCHIVE') : I18n.t('ARCHIVE') }
|
{ archived ? I18n.t('UNARCHIVE') : I18n.t('ARCHIVE') }
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Status from '../../containers/Status';
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
import database from '../../lib/database';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
@ -58,15 +58,13 @@ class RoomInfoView extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
const room = props.navigation.getParam('room');
|
||||||
this.rid = props.navigation.getParam('rid');
|
this.rid = props.navigation.getParam('rid');
|
||||||
this.t = props.navigation.getParam('t');
|
this.t = props.navigation.getParam('t');
|
||||||
this.roles = database.objects('roles');
|
|
||||||
this.sub = {
|
|
||||||
unsubscribe: () => {}
|
|
||||||
};
|
|
||||||
this.state = {
|
this.state = {
|
||||||
room: {},
|
room: room || {},
|
||||||
roomUser: {}
|
roomUser: {},
|
||||||
|
parsedRoles: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,19 +75,29 @@ class RoomInfoView extends React.Component {
|
||||||
try {
|
try {
|
||||||
const result = await RocketChat.getUserInfo(roomUserId);
|
const result = await RocketChat.getUserInfo(roomUserId);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.setState({ roomUser: result.user });
|
const { roles } = result.user;
|
||||||
|
let parsedRoles = [];
|
||||||
|
if (roles && roles.length) {
|
||||||
|
parsedRoles = await Promise.all(roles.map(async(role) => {
|
||||||
|
const description = await this.getRoleDescription(role);
|
||||||
|
return description;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
this.setState({ roomUser: result.user, parsedRoles });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
const { navigation } = this.props;
|
||||||
safeAddListener(this.rooms, this.updateRoom);
|
let room = navigation.getParam('room');
|
||||||
let room = {};
|
if (room && room.observe) {
|
||||||
if (this.rooms.length > 0) {
|
this.roomObservable = room.observe();
|
||||||
this.setState({ room: this.rooms[0] });
|
this.subscription = this.roomObservable
|
||||||
[room] = this.rooms;
|
.subscribe((changes) => {
|
||||||
|
this.setState({ room: changes });
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const result = await RocketChat.getRoomInfo(this.rid);
|
const result = await RocketChat.getRoomInfo(this.rid);
|
||||||
|
@ -102,29 +110,34 @@ class RoomInfoView extends React.Component {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
const permissions = await RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
||||||
if (permissions[PERMISSION_EDIT_ROOM] && !room.prid) {
|
if (permissions[PERMISSION_EDIT_ROOM] && !room.prid) {
|
||||||
const { navigation } = this.props;
|
|
||||||
navigation.setParams({ showEdit: true });
|
navigation.setParams({ showEdit: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getRoleDescription = (id) => {
|
componentWillUnmount() {
|
||||||
const role = database.objectForPrimaryKey('roles', id);
|
if (this.subscription && this.subscription.unsubscribe) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoleDescription = async(id) => {
|
||||||
|
const db = database.active;
|
||||||
|
try {
|
||||||
|
const rolesCollection = db.collections.get('roles');
|
||||||
|
const role = await rolesCollection.find(id);
|
||||||
if (role) {
|
if (role) {
|
||||||
return role.description;
|
return role.description;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isDirect = () => this.t === 'd'
|
isDirect = () => this.t === 'd'
|
||||||
|
|
||||||
updateRoom = () => {
|
|
||||||
if (this.rooms.length > 0) {
|
|
||||||
this.setState({ room: JSON.parse(JSON.stringify(this.rooms[0])) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderItem = (key, room) => (
|
renderItem = (key, room) => (
|
||||||
<View style={styles.item}>
|
<View style={styles.item}>
|
||||||
<Text style={styles.itemLabel}>{I18n.t(camelize(key))}</Text>
|
<Text style={styles.itemLabel}>{I18n.t(camelize(key))}</Text>
|
||||||
|
@ -136,12 +149,11 @@ class RoomInfoView extends React.Component {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
renderRole = (role) => {
|
renderRole = (description) => {
|
||||||
const description = this.getRoleDescription(role);
|
|
||||||
if (description) {
|
if (description) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.roleBadge} key={role}>
|
<View style={styles.roleBadge} key={description}>
|
||||||
<Text style={styles.role}>{ this.getRoleDescription(role) }</Text>
|
<Text style={styles.role}>{ description }</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -149,13 +161,13 @@ class RoomInfoView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderRoles = () => {
|
renderRoles = () => {
|
||||||
const { roomUser } = this.state;
|
const { parsedRoles } = this.state;
|
||||||
if (roomUser && roomUser.roles && roomUser.roles.length) {
|
if (parsedRoles && parsedRoles.length) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.item}>
|
<View style={styles.item}>
|
||||||
<Text style={styles.itemLabel}>{I18n.t('Roles')}</Text>
|
<Text style={styles.itemLabel}>{I18n.t('Roles')}</Text>
|
||||||
<View style={styles.rolesContainer}>
|
<View style={styles.rolesContainer}>
|
||||||
{roomUser.roles.map(role => this.renderRole(role))}
|
{parsedRoles.map(role => this.renderRole(role))}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,14 +4,14 @@ import { FlatList, View, ActivityIndicator } from 'react-native';
|
||||||
import ActionSheet from 'react-native-action-sheet';
|
import ActionSheet from 'react-native-action-sheet';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
import equal from 'deep-equal';
|
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import UserItem from '../../presentation/UserItem';
|
import UserItem from '../../presentation/UserItem';
|
||||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
import database from '../../lib/database';
|
||||||
import { LISTENER } from '../../containers/Toast';
|
import { LISTENER } from '../../containers/Toast';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
@ -52,13 +52,25 @@ class RoomMembersView extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.mounted = false;
|
||||||
this.CANCEL_INDEX = 0;
|
this.CANCEL_INDEX = 0;
|
||||||
this.MUTE_INDEX = 1;
|
this.MUTE_INDEX = 1;
|
||||||
this.actionSheetOptions = [''];
|
this.actionSheetOptions = [''];
|
||||||
const { rid } = props.navigation.state.params;
|
const { rid } = props.navigation.state.params;
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
|
|
||||||
this.permissions = RocketChat.hasPermission(['mute-user'], rid);
|
const room = props.navigation.getParam('room');
|
||||||
|
if (room && room.observe) {
|
||||||
|
this.roomObservable = room.observe();
|
||||||
|
this.subscription = this.roomObservable
|
||||||
|
.subscribe((changes) => {
|
||||||
|
if (this.mounted) {
|
||||||
|
this.setState({ room: changes });
|
||||||
|
} else {
|
||||||
|
this.state.room = changes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
allUsers: false,
|
allUsers: false,
|
||||||
|
@ -67,53 +79,25 @@ class RoomMembersView extends React.Component {
|
||||||
members: [],
|
members: [],
|
||||||
membersFiltered: [],
|
membersFiltered: [],
|
||||||
userLongPressed: {},
|
userLongPressed: {},
|
||||||
room: this.rooms[0] || {},
|
room: room || {},
|
||||||
options: [],
|
|
||||||
end: false
|
end: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
async componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
this.fetchMembers();
|
this.fetchMembers();
|
||||||
safeAddListener(this.rooms, this.updateRoom);
|
|
||||||
|
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
|
const { rid } = navigation.state.params;
|
||||||
navigation.setParams({ toggleStatus: this.toggleStatus });
|
navigation.setParams({ toggleStatus: this.toggleStatus });
|
||||||
}
|
this.permissions = await RocketChat.hasPermission(['mute-user'], rid);
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
const {
|
|
||||||
allUsers, filtering, members, membersFiltered, userLongPressed, room, options, isLoading
|
|
||||||
} = this.state;
|
|
||||||
if (nextState.allUsers !== allUsers) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (nextState.filtering !== filtering) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.members, members)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.options, options)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.membersFiltered, membersFiltered)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.userLongPressed, userLongPressed)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.room.muted, room.muted)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (isLoading !== nextState.isLoading) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.rooms.removeAllListeners();
|
if (this.subscription && this.subscription.unsubscribe) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchChangeText = protectedFunction((text) => {
|
onSearchChangeText = protectedFunction((text) => {
|
||||||
|
@ -128,9 +112,12 @@ class RoomMembersView extends React.Component {
|
||||||
|
|
||||||
onPressUser = async(item) => {
|
onPressUser = async(item) => {
|
||||||
try {
|
try {
|
||||||
const subscriptions = database.objects('subscriptions').filtered('name = $0', item.username);
|
const db = database.active;
|
||||||
if (subscriptions.length) {
|
const subsCollection = db.collections.get('subscriptions');
|
||||||
this.goRoom({ rid: subscriptions[0].rid, name: item.username });
|
const query = await subsCollection.query(Q.where('name', item.username)).fetch();
|
||||||
|
if (query) {
|
||||||
|
const [room] = query;
|
||||||
|
this.goRoom({ rid: room.rid, name: item.username, room });
|
||||||
} else {
|
} else {
|
||||||
const result = await RocketChat.createDirectMessage(item.username);
|
const result = await RocketChat.createDirectMessage(item.username);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
@ -150,7 +137,7 @@ class RoomMembersView extends React.Component {
|
||||||
const { muted } = room;
|
const { muted } = room;
|
||||||
|
|
||||||
this.actionSheetOptions = [I18n.t('Cancel')];
|
this.actionSheetOptions = [I18n.t('Cancel')];
|
||||||
const userIsMuted = !!muted.find(m => m === user.username);
|
const userIsMuted = !!(muted || []).find(m => m === user.username);
|
||||||
user.muted = userIsMuted;
|
user.muted = userIsMuted;
|
||||||
if (userIsMuted) {
|
if (userIsMuted) {
|
||||||
this.actionSheetOptions.push(I18n.t('Unmute'));
|
this.actionSheetOptions.push(I18n.t('Unmute'));
|
||||||
|
@ -209,17 +196,12 @@ class RoomMembersView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRoom = () => {
|
goRoom = async({ rid, name, room }) => {
|
||||||
if (this.rooms.length > 0) {
|
|
||||||
const [room] = this.rooms;
|
|
||||||
this.setState({ room });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
goRoom = async({ rid, name }) => {
|
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
await navigation.popToTop();
|
await navigation.popToTop();
|
||||||
navigation.navigate('RoomView', { rid, name, t: 'd' });
|
navigation.navigate('RoomView', {
|
||||||
|
rid, name, t: 'd', room
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMute = async() => {
|
handleMute = async() => {
|
||||||
|
|
|
@ -10,14 +10,15 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const EmptyRoom = React.memo(({ length }) => {
|
const EmptyRoom = React.memo(({ length, mounted }) => {
|
||||||
if (length === 0) {
|
if (length === 0 && mounted) {
|
||||||
return <ImageBackground source={{ uri: 'message_empty' }} style={styles.image} />;
|
return <ImageBackground source={{ uri: 'message_empty' }} style={styles.image} />;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
EmptyRoom.propTypes = {
|
EmptyRoom.propTypes = {
|
||||||
length: PropTypes.number.isRequired
|
length: PropTypes.number.isRequired,
|
||||||
|
mounted: PropTypes.bool
|
||||||
};
|
};
|
||||||
export default EmptyRoom;
|
export default EmptyRoom;
|
||||||
|
|
|
@ -47,19 +47,18 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
const Typing = React.memo(({ usersTyping }) => {
|
const Typing = React.memo(({ usersTyping }) => {
|
||||||
const users = usersTyping.map(item => item.username);
|
|
||||||
let usersText;
|
let usersText;
|
||||||
if (!users.length) {
|
if (!usersTyping.length) {
|
||||||
return null;
|
return null;
|
||||||
} else if (users.length === 2) {
|
} else if (usersTyping.length === 2) {
|
||||||
usersText = users.join(` ${ I18n.t('and') } `);
|
usersText = usersTyping.join(` ${ I18n.t('and') } `);
|
||||||
} else {
|
} else {
|
||||||
usersText = users.join(', ');
|
usersText = usersTyping.join(', ');
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Text style={styles.typing} numberOfLines={1}>
|
<Text style={styles.typing} numberOfLines={1}>
|
||||||
<Text style={styles.typingUsers}>{usersText} </Text>
|
<Text style={styles.typingUsers}>{usersText} </Text>
|
||||||
{ users.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing') }...
|
{ usersTyping.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing') }...
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { CustomHeaderButtons, Item } from '../../../containers/HeaderButton';
|
import { CustomHeaderButtons, Item } from '../../../containers/HeaderButton';
|
||||||
import database, { safeAddListener } from '../../../lib/realm';
|
import database from '../../../lib/database';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
more: {
|
more: {
|
||||||
|
@ -27,29 +27,33 @@ class RightButtonsContainer extends React.PureComponent {
|
||||||
t: PropTypes.string,
|
t: PropTypes.string,
|
||||||
tmid: PropTypes.string,
|
tmid: PropTypes.string,
|
||||||
navigation: PropTypes.object,
|
navigation: PropTypes.object,
|
||||||
toggleFollowThread: PropTypes.func
|
toggleFollowThread: PropTypes.func,
|
||||||
|
room: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
if (props.tmid) {
|
|
||||||
// FIXME: it may be empty if the thread header isn't fetched yet
|
|
||||||
this.thread = database.objectForPrimaryKey('messages', props.tmid);
|
|
||||||
}
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isFollowingThread: true
|
isFollowingThread: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
async componentDidMount() {
|
||||||
if (this.thread) {
|
const { tmid, userId } = this.props;
|
||||||
safeAddListener(this.thread, this.updateThread);
|
if (tmid) {
|
||||||
|
const db = database.active;
|
||||||
|
const threadObservable = await db.collections.get('messages').findAndObserve(tmid);
|
||||||
|
this.threadSubscription = threadObservable.subscribe((thread) => {
|
||||||
|
this.setState({
|
||||||
|
isFollowingThread: thread.replies && !!thread.replies.find(t => t === userId)
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.thread && this.thread.removeAllListeners) {
|
if (this.threadSubscription && this.threadSubscription.unsubscribe) {
|
||||||
this.thread.removeAllListeners();
|
this.threadSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,8 +70,10 @@ class RightButtonsContainer extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
goRoomActionsView = () => {
|
goRoomActionsView = () => {
|
||||||
const { rid, t, navigation } = this.props;
|
const {
|
||||||
navigation.navigate('RoomActionsView', { rid, t });
|
rid, t, navigation, room
|
||||||
|
} = this.props;
|
||||||
|
navigation.navigate('RoomActionsView', { rid, t, room });
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFollowThread = () => {
|
toggleFollowThread = () => {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
||||||
import { responsive } from 'react-native-responsive-ui';
|
import { responsive } from 'react-native-responsive-ui';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
|
|
||||||
import database, { safeAddListener } from '../../../lib/realm';
|
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import RightButtons from './RightButtons';
|
import RightButtons from './RightButtons';
|
||||||
|
|
||||||
|
@ -14,32 +13,15 @@ class RoomHeaderView extends Component {
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
prid: PropTypes.string,
|
prid: PropTypes.string,
|
||||||
tmid: PropTypes.string,
|
tmid: PropTypes.string,
|
||||||
rid: PropTypes.string,
|
usersTyping: PropTypes.string,
|
||||||
window: PropTypes.object,
|
window: PropTypes.object,
|
||||||
status: PropTypes.string,
|
status: PropTypes.string,
|
||||||
connecting: PropTypes.bool,
|
connecting: PropTypes.bool,
|
||||||
widthOffset: PropTypes.number,
|
widthOffset: PropTypes.number
|
||||||
isLoggedUser: PropTypes.bool,
|
|
||||||
userId: PropTypes.string
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
shouldComponentUpdate(nextProps) {
|
||||||
super(props);
|
const { usersTyping } = this.props;
|
||||||
this.usersTyping = database.memoryDatabase.objects('usersTyping').filtered('rid = $0', props.rid);
|
|
||||||
this.user = [];
|
|
||||||
if (props.type === 'd' && !props.isLoggedUser) {
|
|
||||||
this.user = database.memoryDatabase.objects('activeUsers').filtered('id == $0', props.userId);
|
|
||||||
safeAddListener(this.user, this.updateUser);
|
|
||||||
}
|
|
||||||
this.state = {
|
|
||||||
usersTyping: this.usersTyping.slice() || [],
|
|
||||||
user: this.user[0] || {}
|
|
||||||
};
|
|
||||||
this.usersTyping.addListener(this.updateState);
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
const { usersTyping, user } = this.state;
|
|
||||||
const {
|
const {
|
||||||
type, title, status, window, connecting
|
type, title, status, window, connecting
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -61,46 +43,16 @@ class RoomHeaderView extends Component {
|
||||||
if (nextProps.window.height !== window.height) {
|
if (nextProps.window.height !== window.height) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!equal(nextState.usersTyping, usersTyping)) {
|
if (!equal(nextProps.usersTyping, usersTyping)) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.user, user)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.usersTyping.removeAllListeners();
|
|
||||||
if (this.user && this.user.removeAllListeners) {
|
|
||||||
this.user.removeAllListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateState = () => {
|
|
||||||
this.setState({ usersTyping: this.usersTyping.slice() });
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUser = () => {
|
|
||||||
if (this.user.length) {
|
|
||||||
this.setState({ user: this.user[0] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { usersTyping, user } = this.state;
|
|
||||||
const {
|
const {
|
||||||
window, title, type, prid, tmid, widthOffset, isLoggedUser, status: userStatus, connecting
|
window, title, type, prid, tmid, widthOffset, status = 'offline', connecting, usersTyping
|
||||||
} = this.props;
|
} = this.props;
|
||||||
let status = 'offline';
|
|
||||||
|
|
||||||
if (type === 'd') {
|
|
||||||
if (isLoggedUser) {
|
|
||||||
status = userStatus;
|
|
||||||
} else {
|
|
||||||
status = user.status || 'offline';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Header
|
<Header
|
||||||
|
@ -121,24 +73,18 @@ class RoomHeaderView extends Component {
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
let status;
|
let status;
|
||||||
let userId;
|
|
||||||
let isLoggedUser = false;
|
|
||||||
const { rid, type } = ownProps;
|
const { rid, type } = ownProps;
|
||||||
if (type === 'd') {
|
if (type === 'd') {
|
||||||
if (state.login.user && state.login.user.id) {
|
if (state.login.user && state.login.user.id) {
|
||||||
const { id: loggedUserId } = state.login.user;
|
const { id: loggedUserId } = state.login.user;
|
||||||
userId = rid.replace(loggedUserId, '').trim();
|
const userId = rid.replace(loggedUserId, '').trim();
|
||||||
isLoggedUser = userId === loggedUserId;
|
status = state.activeUsers[userId];
|
||||||
if (isLoggedUser) {
|
|
||||||
status = state.login.user.status; // eslint-disable-line
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connecting: state.meteor.connecting,
|
connecting: state.meteor.connecting,
|
||||||
userId,
|
usersTyping: state.usersTyping,
|
||||||
isLoggedUser,
|
|
||||||
status
|
status
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,88 +1,134 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ActivityIndicator, FlatList, InteractionManager } from 'react-native';
|
import {
|
||||||
|
ActivityIndicator, FlatList, InteractionManager, LayoutAnimation
|
||||||
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
import orderBy from 'lodash/orderBy';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
import database from '../../lib/database';
|
||||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import EmptyRoom from './EmptyRoom';
|
import EmptyRoom from './EmptyRoom';
|
||||||
|
import { isIOS } from '../../utils/deviceInfo';
|
||||||
|
|
||||||
export class List extends React.PureComponent {
|
export class List extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onEndReached: PropTypes.func,
|
onEndReached: PropTypes.func,
|
||||||
renderFooter: PropTypes.func,
|
renderFooter: PropTypes.func,
|
||||||
renderRow: PropTypes.func,
|
renderRow: PropTypes.func,
|
||||||
rid: PropTypes.string,
|
rid: PropTypes.string,
|
||||||
t: PropTypes.string,
|
t: PropTypes.string,
|
||||||
tmid: PropTypes.string
|
tmid: PropTypes.string,
|
||||||
|
animated: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
console.time(`${ this.constructor.name } init`);
|
console.time(`${ this.constructor.name } init`);
|
||||||
console.time(`${ this.constructor.name } mount`);
|
console.time(`${ this.constructor.name } mount`);
|
||||||
if (props.tmid) {
|
|
||||||
this.data = database
|
|
||||||
.objects('threadMessages')
|
|
||||||
.filtered('rid = $0', props.tmid)
|
|
||||||
.sorted('ts', true);
|
|
||||||
this.threads = database.objects('threads').filtered('_id = $0', props.tmid);
|
|
||||||
} else {
|
|
||||||
this.data = database
|
|
||||||
.objects('messages')
|
|
||||||
.filtered('rid = $0', props.rid)
|
|
||||||
.sorted('ts', true);
|
|
||||||
this.threads = database.objects('threads').filtered('rid = $0', props.rid);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this.mounted = false;
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: true,
|
loading: true,
|
||||||
end: false,
|
end: false,
|
||||||
messages: this.data.slice(),
|
messages: []
|
||||||
threads: this.threads.slice()
|
|
||||||
};
|
};
|
||||||
|
this.init();
|
||||||
safeAddListener(this.data, this.updateState);
|
|
||||||
console.timeEnd(`${ this.constructor.name } init`);
|
console.timeEnd(`${ this.constructor.name } init`);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
console.timeEnd(`${ this.constructor.name } mount`);
|
console.timeEnd(`${ this.constructor.name } mount`);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
// eslint-disable-next-line react/sort-comp
|
||||||
this.data.removeAllListeners();
|
async init() {
|
||||||
this.threads.removeAllListeners();
|
const { rid, tmid } = this.props;
|
||||||
if (this.updateState && this.updateState.stop) {
|
const db = database.active;
|
||||||
this.updateState.stop();
|
|
||||||
|
if (tmid) {
|
||||||
|
try {
|
||||||
|
this.thread = await db.collections
|
||||||
|
.get('threads')
|
||||||
|
.find(tmid);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
}
|
}
|
||||||
if (this.interactionManagerState && this.interactionManagerState.cancel) {
|
this.messagesObservable = db.collections
|
||||||
this.interactionManagerState.cancel();
|
.get('thread_messages')
|
||||||
|
.query(
|
||||||
|
Q.where('rid', tmid)
|
||||||
|
)
|
||||||
|
.observeWithColumns(['_updated_at']);
|
||||||
|
} else {
|
||||||
|
this.messagesObservable = db.collections
|
||||||
|
.get('messages')
|
||||||
|
.query(
|
||||||
|
Q.where('rid', rid)
|
||||||
|
)
|
||||||
|
.observeWithColumns(['_updated_at']);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messagesSubscription = this.messagesObservable
|
||||||
|
.subscribe((data) => {
|
||||||
|
this.interaction = InteractionManager.runAfterInteractions(() => {
|
||||||
|
if (tmid) {
|
||||||
|
data = [this.thread, ...data];
|
||||||
|
}
|
||||||
|
const messages = orderBy(data, ['ts'], ['desc']);
|
||||||
|
if (this.mounted) {
|
||||||
|
LayoutAnimation.easeInEaseOut();
|
||||||
|
this.setState({ messages });
|
||||||
|
} else {
|
||||||
|
this.state.messages = messages;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// this.state.loading works for this.onEndReached and RoomView.init
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
if (props.loading !== state.loading) {
|
||||||
|
return {
|
||||||
|
loading: props.loading
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
const { messages, loading, end } = this.state;
|
||||||
|
if (loading !== nextState.loading) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (end !== nextState.end) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!isEqual(messages, nextState.messages)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.messagesSubscription && this.messagesSubscription.unsubscribe) {
|
||||||
|
this.messagesSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.interaction && this.interaction.cancel) {
|
||||||
|
this.interaction.cancel();
|
||||||
|
}
|
||||||
|
if (this.onEndReached && this.onEndReached.stop) {
|
||||||
|
this.onEndReached.stop();
|
||||||
}
|
}
|
||||||
console.countReset(`${ this.constructor.name }.render calls`);
|
console.countReset(`${ this.constructor.name }.render calls`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
|
||||||
updateState = debounce(() => {
|
|
||||||
this.interactionManagerState = InteractionManager.runAfterInteractions(() => {
|
|
||||||
const { tmid } = this.props;
|
|
||||||
let messages = this.data;
|
|
||||||
if (tmid && this.threads[0]) {
|
|
||||||
const thread = { ...this.threads[0] };
|
|
||||||
thread.tlm = null;
|
|
||||||
messages = [...messages, thread];
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
messages: messages.slice(),
|
|
||||||
threads: this.threads.slice(),
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, 300, { leading: true });
|
|
||||||
|
|
||||||
onEndReached = debounce(async() => {
|
onEndReached = debounce(async() => {
|
||||||
const {
|
const {
|
||||||
loading, end, messages
|
loading, end, messages
|
||||||
|
@ -97,7 +143,7 @@ export class List extends React.PureComponent {
|
||||||
let result;
|
let result;
|
||||||
if (tmid) {
|
if (tmid) {
|
||||||
// `offset` is `messages.length - 1` because we append thread start to `messages` obj
|
// `offset` is `messages.length - 1` because we append thread start to `messages` obj
|
||||||
result = await RocketChat.loadThreadMessages({ tmid, offset: messages.length - 1 });
|
result = await RocketChat.loadThreadMessages({ tmid, rid, offset: messages.length - 1 });
|
||||||
} else {
|
} else {
|
||||||
result = await RocketChat.loadMessagesForRoom({ rid, t, latest: messages[messages.length - 1].ts });
|
result = await RocketChat.loadMessagesForRoom({ rid, t, latest: messages[messages.length - 1].ts });
|
||||||
}
|
}
|
||||||
|
@ -118,15 +164,8 @@ export class List extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem = ({ item, index }) => {
|
renderItem = ({ item, index }) => {
|
||||||
const { messages, threads } = this.state;
|
const { messages } = this.state;
|
||||||
const { renderRow } = this.props;
|
const { renderRow } = this.props;
|
||||||
if (item.tmid) {
|
|
||||||
const thread = threads.find(t => t._id === item.tmid);
|
|
||||||
if (thread) {
|
|
||||||
const tmsg = thread.msg || (thread.attachments && thread.attachments.length && thread.attachments[0].title);
|
|
||||||
item = { ...item, tmsg };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return renderRow(item, messages[index + 1]);
|
return renderRow(item, messages[index + 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,19 +173,19 @@ export class List extends React.PureComponent {
|
||||||
console.count(`${ this.constructor.name }.render calls`);
|
console.count(`${ this.constructor.name }.render calls`);
|
||||||
const { messages } = this.state;
|
const { messages } = this.state;
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
<EmptyRoom length={messages.length} />
|
<EmptyRoom length={messages.length} mounted={this.mounted} />
|
||||||
<FlatList
|
<FlatList
|
||||||
testID='room-view-messages'
|
testID='room-view-messages'
|
||||||
ref={ref => this.list = ref}
|
ref={ref => this.list = ref}
|
||||||
keyExtractor={item => item._id}
|
keyExtractor={item => item.id}
|
||||||
data={messages}
|
data={messages}
|
||||||
extraData={this.state}
|
extraData={this.state}
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
contentContainerStyle={styles.contentContainer}
|
contentContainerStyle={styles.contentContainer}
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
inverted
|
inverted
|
||||||
removeClippedSubviews
|
removeClippedSubviews={isIOS}
|
||||||
initialNumToRender={7}
|
initialNumToRender={7}
|
||||||
onEndReached={this.onEndReached}
|
onEndReached={this.onEndReached}
|
||||||
onEndReachedThreshold={5}
|
onEndReachedThreshold={5}
|
||||||
|
@ -155,7 +194,7 @@ export class List extends React.PureComponent {
|
||||||
ListFooterComponent={this.renderFooter}
|
ListFooterComponent={this.renderFooter}
|
||||||
{...scrollPersistTaps}
|
{...scrollPersistTaps}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import Modal from 'react-native-modal';
|
||||||
import { responsive } from 'react-native-responsive-ui';
|
import { responsive } from 'react-native-responsive-ui';
|
||||||
|
|
||||||
import EmojiPicker from '../../containers/EmojiPicker';
|
import EmojiPicker from '../../containers/EmojiPicker';
|
||||||
import { toggleReactionPicker as toggleReactionPickerAction } from '../../actions/messages';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { isAndroid } from '../../utils/deviceInfo';
|
import { isAndroid } from '../../utils/deviceInfo';
|
||||||
|
|
||||||
|
@ -17,36 +16,37 @@ class ReactionPicker extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
baseUrl: PropTypes.string.isRequired,
|
baseUrl: PropTypes.string.isRequired,
|
||||||
window: PropTypes.any,
|
window: PropTypes.any,
|
||||||
showReactionPicker: PropTypes.bool,
|
message: PropTypes.object,
|
||||||
toggleReactionPicker: PropTypes.func,
|
show: PropTypes.bool,
|
||||||
|
reactionClose: PropTypes.func,
|
||||||
onEmojiSelected: PropTypes.func
|
onEmojiSelected: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
const { showReactionPicker, window } = this.props;
|
const { show, window } = this.props;
|
||||||
return nextProps.showReactionPicker !== showReactionPicker || window.width !== nextProps.window.width;
|
return nextProps.show !== show || window.width !== nextProps.window.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
onEmojiSelected(emoji, shortname) {
|
onEmojiSelected(emoji, shortname) {
|
||||||
// standard emojis: `emoji` is unicode and `shortname` is :joy:
|
// standard emojis: `emoji` is unicode and `shortname` is :joy:
|
||||||
// custom emojis: only `emoji` is returned with shortname type (:joy:)
|
// custom emojis: only `emoji` is returned with shortname type (:joy:)
|
||||||
// to set reactions, we need shortname type
|
// to set reactions, we need shortname type
|
||||||
const { onEmojiSelected } = this.props;
|
const { onEmojiSelected, message } = this.props;
|
||||||
onEmojiSelected(shortname || emoji);
|
onEmojiSelected(shortname || emoji, message.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
window: { width, height }, showReactionPicker, baseUrl, toggleReactionPicker
|
window: { width, height }, show, baseUrl, reactionClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (showReactionPicker
|
return (show
|
||||||
? (
|
? (
|
||||||
<Modal
|
<Modal
|
||||||
isVisible={showReactionPicker}
|
isVisible={show}
|
||||||
style={{ alignItems: 'center' }}
|
style={{ alignItems: 'center' }}
|
||||||
onBackdropPress={() => toggleReactionPicker()}
|
onBackdropPress={reactionClose}
|
||||||
onBackButtonPress={() => toggleReactionPicker()}
|
onBackButtonPress={reactionClose}
|
||||||
animationIn='fadeIn'
|
animationIn='fadeIn'
|
||||||
animationOut='fadeOut'
|
animationOut='fadeOut'
|
||||||
>
|
>
|
||||||
|
@ -69,12 +69,7 @@ class ReactionPicker extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
showReactionPicker: state.messages.showReactionPicker,
|
|
||||||
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
export default responsive(connect(mapStateToProps)(ReactionPicker));
|
||||||
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message))
|
|
||||||
});
|
|
||||||
|
|
||||||
export default responsive(connect(mapStateToProps, mapDispatchToProps)(ReactionPicker));
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { responsive } from 'react-native-responsive-ui';
|
import { responsive } from 'react-native-responsive-ui';
|
||||||
import equal from 'deep-equal';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
import database from '../../lib/database';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
@ -74,47 +74,72 @@ class UploadProgress extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.mounted = false;
|
||||||
|
this.ranInitialUploadCheck = false;
|
||||||
|
this.init();
|
||||||
this.state = {
|
this.state = {
|
||||||
uploads: []
|
uploads: []
|
||||||
};
|
};
|
||||||
const { rid } = this.props;
|
|
||||||
this.uploads = database.objects('uploads').filtered('rid = $0', rid);
|
|
||||||
safeAddListener(this.uploads, this.updateUploads);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.uploads.forEach((u) => {
|
this.mounted = true;
|
||||||
if (!RocketChat.isUploadActive(u.path)) {
|
|
||||||
database.write(() => {
|
|
||||||
const [upload] = database.objects('uploads').filtered('path = $0', u.path);
|
|
||||||
if (upload) {
|
|
||||||
upload.error = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
|
||||||
const { uploads } = this.state;
|
|
||||||
const { window } = this.props;
|
|
||||||
if (nextProps.window.width !== window.width) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!equal(nextState.uploads, uploads)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.uploads.removeAllListeners();
|
if (this.uploadsSubscription && this.uploadsSubscription.unsubscribe) {
|
||||||
|
this.uploadsSubscription.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteUpload = (item) => {
|
init = () => {
|
||||||
const uploadItem = this.uploads.filtered('path = $0', item.path);
|
const { rid } = this.props;
|
||||||
|
|
||||||
|
const db = database.active;
|
||||||
|
this.uploadsObservable = db.collections
|
||||||
|
.get('uploads')
|
||||||
|
.query(
|
||||||
|
Q.where('rid', rid)
|
||||||
|
)
|
||||||
|
.observeWithColumns(['progress', 'error']);
|
||||||
|
|
||||||
|
this.uploadsSubscription = this.uploadsObservable
|
||||||
|
.subscribe((uploads) => {
|
||||||
|
if (this.mounted) {
|
||||||
|
this.setState({ uploads });
|
||||||
|
} else {
|
||||||
|
this.state.uploads = uploads;
|
||||||
|
}
|
||||||
|
if (!this.ranInitialUploadCheck) {
|
||||||
|
this.uploadCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadCheck = () => {
|
||||||
|
this.ranInitialUploadCheck = true;
|
||||||
|
const { uploads } = this.state;
|
||||||
|
uploads.forEach(async(u) => {
|
||||||
|
if (!RocketChat.isUploadActive(u.path)) {
|
||||||
try {
|
try {
|
||||||
database.write(() => database.delete(uploadItem[0]));
|
await database.database.action(async() => {
|
||||||
|
await u.update(() => {
|
||||||
|
u.error = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteUpload = async(item) => {
|
||||||
|
try {
|
||||||
|
const db = database.active;
|
||||||
|
await db.action(async() => {
|
||||||
|
await item.destroyPermanently();
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
@ -122,7 +147,7 @@ class UploadProgress extends Component {
|
||||||
|
|
||||||
cancelUpload = async(item) => {
|
cancelUpload = async(item) => {
|
||||||
try {
|
try {
|
||||||
await RocketChat.cancelUpload(item.path);
|
await RocketChat.cancelUpload(item);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
@ -132,20 +157,18 @@ class UploadProgress extends Component {
|
||||||
const { rid, baseUrl: server, user } = this.props;
|
const { rid, baseUrl: server, user } = this.props;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
database.write(() => {
|
const db = database.active;
|
||||||
|
await db.action(async() => {
|
||||||
|
await item.update(() => {
|
||||||
item.error = false;
|
item.error = false;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
await RocketChat.sendFileMessage(rid, item, undefined, server, user);
|
await RocketChat.sendFileMessage(rid, item, undefined, server, user);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUploads = () => {
|
|
||||||
const uploads = this.uploads.map(item => JSON.parse(JSON.stringify(item)));
|
|
||||||
this.setState({ uploads });
|
|
||||||
}
|
|
||||||
|
|
||||||
renderItemContent = (item) => {
|
renderItemContent = (item) => {
|
||||||
const { window } = this.props;
|
const { window } = this.props;
|
||||||
|
|
||||||
|
@ -177,6 +200,7 @@ class UploadProgress extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: transform into stateless and update based on its own observable changes
|
||||||
renderItem = (item, index) => (
|
renderItem = (item, index) => (
|
||||||
<View key={item.path} style={[styles.item, index !== 0 ? { marginTop: 10 } : {}]}>
|
<View key={item.path} style={[styles.item, index !== 0 ? { marginTop: 10 } : {}]}>
|
||||||
{this.renderItemContent(item)}
|
{this.renderItemContent(item)}
|
||||||
|
|
|
@ -1,26 +1,22 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
Text, View, LayoutAnimation, InteractionManager
|
Text, View, InteractionManager, LayoutAnimation
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { RectButton } from 'react-native-gesture-handler';
|
import { RectButton } from 'react-native-gesture-handler';
|
||||||
import { SafeAreaView, HeaderBackButton } from 'react-navigation';
|
import { SafeAreaView, HeaderBackButton } from 'react-navigation';
|
||||||
import equal from 'deep-equal';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import EJSON from 'ejson';
|
|
||||||
import * as Haptics from 'expo-haptics';
|
import * as Haptics from 'expo-haptics';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
toggleReactionPicker as toggleReactionPickerAction,
|
|
||||||
actionsShow as actionsShowAction,
|
|
||||||
errorActionsShow as errorActionsShowAction,
|
|
||||||
editCancel as editCancelAction,
|
|
||||||
replyCancel as replyCancelAction,
|
|
||||||
replyBroadcast as replyBroadcastAction
|
replyBroadcast as replyBroadcastAction
|
||||||
} from '../../actions/messages';
|
} from '../../actions/messages';
|
||||||
import { List } from './List';
|
import { List } from './List';
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
import database from '../../lib/database';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import Message from '../../containers/message';
|
import Message from '../../containers/message';
|
||||||
import MessageActions from '../../containers/MessageActions';
|
import MessageActions from '../../containers/MessageActions';
|
||||||
|
@ -30,7 +26,6 @@ import ReactionPicker from './ReactionPicker';
|
||||||
import UploadProgress from './UploadProgress';
|
import UploadProgress from './UploadProgress';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import RoomHeaderView, { RightButtons } from './Header';
|
import RoomHeaderView, { RightButtons } from './Header';
|
||||||
|
@ -38,11 +33,26 @@ import StatusBar from '../../containers/StatusBar';
|
||||||
import Separator from './Separator';
|
import Separator from './Separator';
|
||||||
import { COLOR_WHITE, HEADER_BACK } from '../../constants/colors';
|
import { COLOR_WHITE, HEADER_BACK } from '../../constants/colors';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import buildMessage from '../../lib/methods/helpers/buildMessage';
|
|
||||||
import FileModal from '../../containers/FileModal';
|
import FileModal from '../../containers/FileModal';
|
||||||
import ReactionsModal from '../../containers/ReactionsModal';
|
import ReactionsModal from '../../containers/ReactionsModal';
|
||||||
import { LISTENER } from '../../containers/Toast';
|
import { LISTENER } from '../../containers/Toast';
|
||||||
import { isReadOnly, isBlocked } from '../../utils/room';
|
import { isReadOnly, isBlocked } from '../../utils/room';
|
||||||
|
import { isIOS } from '../../utils/deviceInfo';
|
||||||
|
|
||||||
|
const stateAttrsUpdate = [
|
||||||
|
'joined',
|
||||||
|
'lastOpen',
|
||||||
|
'photoModalVisible',
|
||||||
|
'reactionsModalVisible',
|
||||||
|
'canAutoTranslate',
|
||||||
|
'showActions',
|
||||||
|
'showErrorActions',
|
||||||
|
'loading',
|
||||||
|
'editing',
|
||||||
|
'replying',
|
||||||
|
'reacting'
|
||||||
|
];
|
||||||
|
const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'muted'];
|
||||||
|
|
||||||
class RoomView extends React.Component {
|
class RoomView extends React.Component {
|
||||||
static navigationOptions = ({ navigation }) => {
|
static navigationOptions = ({ navigation }) => {
|
||||||
|
@ -51,6 +61,7 @@ class RoomView extends React.Component {
|
||||||
const title = navigation.getParam('name');
|
const title = navigation.getParam('name');
|
||||||
const t = navigation.getParam('t');
|
const t = navigation.getParam('t');
|
||||||
const tmid = navigation.getParam('tmid');
|
const tmid = navigation.getParam('tmid');
|
||||||
|
const room = navigation.getParam('room');
|
||||||
const toggleFollowThread = navigation.getParam('toggleFollowThread', () => {});
|
const toggleFollowThread = navigation.getParam('toggleFollowThread', () => {});
|
||||||
const unreadsCount = navigation.getParam('unreadsCount', null);
|
const unreadsCount = navigation.getParam('unreadsCount', null);
|
||||||
return {
|
return {
|
||||||
|
@ -68,6 +79,7 @@ class RoomView extends React.Component {
|
||||||
<RightButtons
|
<RightButtons
|
||||||
rid={rid}
|
rid={rid}
|
||||||
tmid={tmid}
|
tmid={tmid}
|
||||||
|
room={room}
|
||||||
t={t}
|
t={t}
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
toggleFollowThread={toggleFollowThread}
|
toggleFollowThread={toggleFollowThread}
|
||||||
|
@ -91,25 +103,16 @@ class RoomView extends React.Component {
|
||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
token: PropTypes.string.isRequired
|
token: PropTypes.string.isRequired
|
||||||
}),
|
}),
|
||||||
showActions: PropTypes.bool,
|
|
||||||
showErrorActions: PropTypes.bool,
|
|
||||||
actionMessage: PropTypes.object,
|
|
||||||
appState: PropTypes.string,
|
appState: PropTypes.string,
|
||||||
useRealName: PropTypes.bool,
|
useRealName: PropTypes.bool,
|
||||||
isAuthenticated: PropTypes.bool,
|
isAuthenticated: PropTypes.bool,
|
||||||
Message_GroupingPeriod: PropTypes.number,
|
Message_GroupingPeriod: PropTypes.number,
|
||||||
Message_TimeFormat: PropTypes.string,
|
Message_TimeFormat: PropTypes.string,
|
||||||
Message_Read_Receipt_Enabled: PropTypes.bool,
|
Message_Read_Receipt_Enabled: PropTypes.bool,
|
||||||
editing: PropTypes.bool,
|
|
||||||
replying: PropTypes.bool,
|
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
|
customEmojis: PropTypes.object,
|
||||||
useMarkdown: PropTypes.bool,
|
useMarkdown: PropTypes.bool,
|
||||||
toggleReactionPicker: PropTypes.func,
|
replyBroadcast: PropTypes.func
|
||||||
actionsShow: PropTypes.func,
|
|
||||||
editCancel: PropTypes.func,
|
|
||||||
replyCancel: PropTypes.func,
|
|
||||||
replyBroadcast: PropTypes.func,
|
|
||||||
errorActionsShow: PropTypes.func
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -118,22 +121,36 @@ class RoomView extends React.Component {
|
||||||
console.time(`${ this.constructor.name } mount`);
|
console.time(`${ this.constructor.name } mount`);
|
||||||
this.rid = props.navigation.getParam('rid');
|
this.rid = props.navigation.getParam('rid');
|
||||||
this.t = props.navigation.getParam('t');
|
this.t = props.navigation.getParam('t');
|
||||||
this.tmid = props.navigation.getParam('tmid');
|
this.tmid = props.navigation.getParam('tmid', null);
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
const room = props.navigation.getParam('room');
|
||||||
this.chats = database.objects('subscriptions').filtered('rid != $0', this.rid);
|
const selectedMessage = props.navigation.getParam('message');
|
||||||
const canAutoTranslate = RocketChat.canAutoTranslate();
|
|
||||||
this.state = {
|
this.state = {
|
||||||
joined: this.rooms.length > 0,
|
joined: true,
|
||||||
room: this.rooms[0] || { rid: this.rid, t: this.t },
|
room: room || { rid: this.rid, t: this.t },
|
||||||
|
roomUpdate: {},
|
||||||
lastOpen: null,
|
lastOpen: null,
|
||||||
photoModalVisible: false,
|
photoModalVisible: false,
|
||||||
reactionsModalVisible: false,
|
reactionsModalVisible: false,
|
||||||
selectedAttachment: {},
|
selectedAttachment: {},
|
||||||
selectedMessage: {},
|
selectedMessage: selectedMessage || {},
|
||||||
canAutoTranslate
|
canAutoTranslate: false,
|
||||||
|
loading: true,
|
||||||
|
showActions: false,
|
||||||
|
showErrorActions: false,
|
||||||
|
editing: false,
|
||||||
|
replying: !!selectedMessage,
|
||||||
|
replyWithMention: false,
|
||||||
|
reacting: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (room && room.observe) {
|
||||||
|
this.observeRoom(room);
|
||||||
|
} else {
|
||||||
|
this.findAndObserveRoom(this.rid);
|
||||||
|
}
|
||||||
|
|
||||||
this.beginAnimating = false;
|
this.beginAnimating = false;
|
||||||
this.beginAnimatingTimeout = setTimeout(() => this.beginAnimating = true, 300);
|
this.didFocusListener = props.navigation.addListener('didFocus', () => this.beginAnimating = true);
|
||||||
this.messagebox = React.createRef();
|
this.messagebox = React.createRef();
|
||||||
this.willBlurListener = props.navigation.addListener('willBlur', () => this.mounted = false);
|
this.willBlurListener = props.navigation.addListener('willBlur', () => this.mounted = false);
|
||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
|
@ -141,111 +158,85 @@ class RoomView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.mounted = true;
|
||||||
this.didMountInteraction = InteractionManager.runAfterInteractions(() => {
|
this.didMountInteraction = InteractionManager.runAfterInteractions(() => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { navigation, isAuthenticated } = this.props;
|
const { navigation, isAuthenticated } = this.props;
|
||||||
|
if (room.id && !this.tmid) {
|
||||||
if (room._id && !this.tmid) {
|
|
||||||
navigation.setParams({ name: this.getRoomTitle(room), t: room.t });
|
navigation.setParams({ name: this.getRoomTitle(room), t: room.t });
|
||||||
}
|
}
|
||||||
if (this.tmid) {
|
if (this.tmid) {
|
||||||
navigation.setParams({ toggleFollowThread: this.toggleFollowThread });
|
navigation.setParams({ toggleFollowThread: this.toggleFollowThread });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
this.init();
|
this.init();
|
||||||
} else {
|
} else {
|
||||||
EventEmitter.addEventListener('connected', this.handleConnected);
|
EventEmitter.addEventListener('connected', this.handleConnected);
|
||||||
}
|
}
|
||||||
safeAddListener(this.rooms, this.updateRoom);
|
this.updateUnreadCount();
|
||||||
safeAddListener(this.chats, this.updateUnreadCount);
|
|
||||||
this.mounted = true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.timeEnd(`${ this.constructor.name } mount`);
|
console.timeEnd(`${ this.constructor.name } mount`);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const {
|
const { state } = this;
|
||||||
room, joined, lastOpen, photoModalVisible, reactionsModalVisible, canAutoTranslate
|
const { roomUpdate } = state;
|
||||||
} = this.state;
|
const { appState } = this.props;
|
||||||
const { showActions, showErrorActions, appState } = this.props;
|
if (appState !== nextProps.appState) {
|
||||||
|
|
||||||
if (lastOpen !== nextState.lastOpen) {
|
|
||||||
return true;
|
|
||||||
} else if (photoModalVisible !== nextState.photoModalVisible) {
|
|
||||||
return true;
|
|
||||||
} else if (reactionsModalVisible !== nextState.reactionsModalVisible) {
|
|
||||||
return true;
|
|
||||||
} else if (room.ro !== nextState.room.ro) {
|
|
||||||
return true;
|
|
||||||
} else if (room.f !== nextState.room.f) {
|
|
||||||
return true;
|
|
||||||
} else if (room.blocked !== nextState.room.blocked) {
|
|
||||||
return true;
|
|
||||||
} else if (room.blocker !== nextState.room.blocker) {
|
|
||||||
return true;
|
|
||||||
} else if (room.archived !== nextState.room.archived) {
|
|
||||||
return true;
|
|
||||||
} else if (joined !== nextState.joined) {
|
|
||||||
return true;
|
|
||||||
} else if (canAutoTranslate !== nextState.canAutoTranslate) {
|
|
||||||
return true;
|
|
||||||
} else if (showActions !== nextProps.showActions) {
|
|
||||||
return true;
|
|
||||||
} else if (showErrorActions !== nextProps.showErrorActions) {
|
|
||||||
return true;
|
|
||||||
} else if (appState !== nextProps.appState) {
|
|
||||||
return true;
|
|
||||||
} else if (!equal(room.muted, nextState.room.muted)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
const stateUpdated = stateAttrsUpdate.some(key => nextState[key] !== state[key]);
|
||||||
|
if (stateUpdated) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return roomAttrsUpdate.some(key => !isEqual(nextState.roomUpdate[key], roomUpdate[key]));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const { room } = this.state;
|
|
||||||
const { appState } = this.props;
|
const { appState } = this.props;
|
||||||
|
|
||||||
if (appState === 'foreground' && appState !== prevProps.appState) {
|
if (appState === 'foreground' && appState !== prevProps.appState) {
|
||||||
this.onForegroundInteraction = InteractionManager.runAfterInteractions(() => {
|
this.onForegroundInteraction = InteractionManager.runAfterInteractions(() => {
|
||||||
RocketChat.loadMissedMessages(room).catch(e => console.log(e));
|
this.init();
|
||||||
RocketChat.readMessages(room.rid).catch(e => console.log(e));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
async componentWillUnmount() {
|
||||||
|
const { editing, room } = this.state;
|
||||||
|
const db = database.active;
|
||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
const { editing, replying } = this.props;
|
|
||||||
if (!editing && this.messagebox && this.messagebox.current) {
|
if (!editing && this.messagebox && this.messagebox.current) {
|
||||||
const { text } = this.messagebox.current;
|
const { text } = this.messagebox.current;
|
||||||
let obj;
|
let obj;
|
||||||
if (this.tmid) {
|
if (this.tmid) {
|
||||||
obj = database.objectForPrimaryKey('threads', this.tmid);
|
try {
|
||||||
|
const threadsCollection = db.collections.get('threads');
|
||||||
|
obj = await threadsCollection.find(this.tmid);
|
||||||
|
} catch (e) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
[obj] = this.rooms;
|
obj = room;
|
||||||
}
|
}
|
||||||
if (obj) {
|
if (obj) {
|
||||||
database.write(() => {
|
try {
|
||||||
obj.draftMessage = text;
|
await db.action(async() => {
|
||||||
|
await obj.update((r) => {
|
||||||
|
r.draftMessage = text;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.rooms.removeAllListeners();
|
|
||||||
this.chats.removeAllListeners();
|
|
||||||
if (this.sub && this.sub.stop) {
|
if (this.sub && this.sub.stop) {
|
||||||
this.sub.stop();
|
this.sub.stop();
|
||||||
}
|
}
|
||||||
if (this.beginAnimatingTimeout) {
|
if (this.didFocusListener && this.didFocusListener.remove) {
|
||||||
clearTimeout(this.beginAnimatingTimeout);
|
this.didFocusListener.remove();
|
||||||
}
|
|
||||||
if (editing) {
|
|
||||||
const { editCancel } = this.props;
|
|
||||||
editCancel();
|
|
||||||
}
|
|
||||||
if (replying) {
|
|
||||||
const { replyCancel } = this.props;
|
|
||||||
replyCancel();
|
|
||||||
}
|
}
|
||||||
if (this.didMountInteraction && this.didMountInteraction.cancel) {
|
if (this.didMountInteraction && this.didMountInteraction.cancel) {
|
||||||
this.didMountInteraction.cancel();
|
this.didMountInteraction.cancel();
|
||||||
|
@ -253,15 +244,18 @@ class RoomView extends React.Component {
|
||||||
if (this.onForegroundInteraction && this.onForegroundInteraction.cancel) {
|
if (this.onForegroundInteraction && this.onForegroundInteraction.cancel) {
|
||||||
this.onForegroundInteraction.cancel();
|
this.onForegroundInteraction.cancel();
|
||||||
}
|
}
|
||||||
if (this.updateStateInteraction && this.updateStateInteraction.cancel) {
|
|
||||||
this.updateStateInteraction.cancel();
|
|
||||||
}
|
|
||||||
if (this.initInteraction && this.initInteraction.cancel) {
|
if (this.initInteraction && this.initInteraction.cancel) {
|
||||||
this.initInteraction.cancel();
|
this.initInteraction.cancel();
|
||||||
}
|
}
|
||||||
if (this.willBlurListener && this.willBlurListener.remove) {
|
if (this.willBlurListener && this.willBlurListener.remove) {
|
||||||
this.willBlurListener.remove();
|
this.willBlurListener.remove();
|
||||||
}
|
}
|
||||||
|
if (this.subSubscription && this.subSubscription.unsubscribe) {
|
||||||
|
this.subSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
if (this.queryUnreads && this.queryUnreads.unsubscribe) {
|
||||||
|
this.queryUnreads.unsubscribe();
|
||||||
|
}
|
||||||
EventEmitter.removeListener('connected', this.handleConnected);
|
EventEmitter.removeListener('connected', this.handleConnected);
|
||||||
console.countReset(`${ this.constructor.name }.render calls`);
|
console.countReset(`${ this.constructor.name }.render calls`);
|
||||||
}
|
}
|
||||||
|
@ -269,38 +263,135 @@ class RoomView extends React.Component {
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
init = () => {
|
init = () => {
|
||||||
try {
|
try {
|
||||||
|
this.setState({ loading: true });
|
||||||
this.initInteraction = InteractionManager.runAfterInteractions(async() => {
|
this.initInteraction = InteractionManager.runAfterInteractions(async() => {
|
||||||
const { room } = this.state;
|
const { room, joined } = this.state;
|
||||||
if (this.tmid) {
|
if (this.tmid) {
|
||||||
await this.getThreadMessages();
|
await this.getThreadMessages();
|
||||||
} else {
|
} else {
|
||||||
|
const newLastOpen = new Date();
|
||||||
await this.getMessages(room);
|
await this.getMessages(room);
|
||||||
|
|
||||||
// if room is joined
|
// if room is joined
|
||||||
if (room._id) {
|
if (joined) {
|
||||||
if (room.alert || room.unread || room.userMentions) {
|
if (room.alert || room.unread || room.userMentions) {
|
||||||
this.setLastOpen(room.ls);
|
this.setLastOpen(room.ls);
|
||||||
} else {
|
} else {
|
||||||
this.setLastOpen(null);
|
this.setLastOpen(null);
|
||||||
}
|
}
|
||||||
RocketChat.readMessages(room.rid).catch(e => console.log(e));
|
RocketChat.readMessages(room.rid, newLastOpen).catch(e => console.log(e));
|
||||||
this.sub = await RocketChat.subscribeRoom(room);
|
this.sub = await RocketChat.subscribeRoom(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We run `canAutoTranslate` again in order to refetch auto translate permission
|
// We run `canAutoTranslate` again in order to refetch auto translate permission
|
||||||
// in case of a missing connection or poor connection on room open
|
// in case of a missing connection or poor connection on room open
|
||||||
const canAutoTranslate = RocketChat.canAutoTranslate();
|
const canAutoTranslate = await RocketChat.canAutoTranslate();
|
||||||
this.setState({ canAutoTranslate });
|
this.setState({ canAutoTranslate, loading: false });
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
this.setState({ loading: false });
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findAndObserveRoom = async(rid) => {
|
||||||
|
try {
|
||||||
|
const db = database.active;
|
||||||
|
const { navigation } = this.props;
|
||||||
|
const subCollection = await db.collections.get('subscriptions');
|
||||||
|
const room = await subCollection.find(rid);
|
||||||
|
this.setState({ room });
|
||||||
|
navigation.setParams({ room });
|
||||||
|
this.observeRoom(room);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.t !== 'd') {
|
||||||
|
console.log('Room not found');
|
||||||
|
this.internalSetState({ joined: false });
|
||||||
|
} else {
|
||||||
|
// We navigate to RoomView before the DM is inserted to the local db
|
||||||
|
// So we retry just to make sure we have the right content
|
||||||
|
this.retryFindCount = this.retryFindCount + 1 || 1;
|
||||||
|
if (this.retryFindCount <= 3) {
|
||||||
|
this.retryFindTimeout = setTimeout(() => {
|
||||||
|
this.findAndObserveRoom(rid);
|
||||||
|
this.init();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observeRoom = (room) => {
|
||||||
|
const observable = room.observe();
|
||||||
|
this.subSubscription = observable
|
||||||
|
.subscribe((changes) => {
|
||||||
|
const roomUpdate = roomAttrsUpdate.reduce((ret, attr) => {
|
||||||
|
ret[attr] = changes[attr];
|
||||||
|
return ret;
|
||||||
|
}, {});
|
||||||
|
if (this.mounted) {
|
||||||
|
this.internalSetState({ room: changes, roomUpdate });
|
||||||
|
} else {
|
||||||
|
this.state.room = changes;
|
||||||
|
this.state.roomUpdate = roomUpdate;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
errorActionsShow = (message) => {
|
||||||
|
this.setState({ selectedMessage: message, showErrorActions: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onActionsHide = () => {
|
||||||
|
const { editing, replying, reacting } = this.state;
|
||||||
|
if (editing || replying || reacting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({ selectedMessage: {}, showActions: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onErrorActionsHide = () => {
|
||||||
|
this.setState({ selectedMessage: {}, showErrorActions: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditInit = (message) => {
|
||||||
|
this.setState({ selectedMessage: message, editing: true, showActions: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditCancel = () => {
|
||||||
|
this.setState({ selectedMessage: {}, editing: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditRequest = async(message) => {
|
||||||
|
this.setState({ selectedMessage: {}, editing: false });
|
||||||
|
try {
|
||||||
|
await RocketChat.editMessage(message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onReplyInit = (message, mention) => {
|
||||||
|
this.setState({
|
||||||
|
selectedMessage: message, replying: true, showActions: false, replyWithMention: mention
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onReplyCancel = () => {
|
||||||
|
this.setState({ selectedMessage: {}, replying: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onReactionInit = (message) => {
|
||||||
|
this.setState({ selectedMessage: message, reacting: true, showActions: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onReactionClose = () => {
|
||||||
|
this.setState({ selectedMessage: {}, reacting: false });
|
||||||
|
}
|
||||||
|
|
||||||
onMessageLongPress = (message) => {
|
onMessageLongPress = (message) => {
|
||||||
const { actionsShow } = this.props;
|
this.setState({ selectedMessage: message, showActions: true });
|
||||||
actionsShow({ ...message, rid: this.rid });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenFileModal = (attachment) => {
|
onOpenFileModal = (attachment) => {
|
||||||
|
@ -311,14 +402,10 @@ class RoomView extends React.Component {
|
||||||
this.setState({ selectedAttachment: {}, photoModalVisible: false });
|
this.setState({ selectedAttachment: {}, photoModalVisible: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onReactionPress = (shortname, messageId) => {
|
onReactionPress = async(shortname, messageId) => {
|
||||||
const { actionMessage, toggleReactionPicker } = this.props;
|
|
||||||
try {
|
try {
|
||||||
if (!messageId) {
|
await RocketChat.setReaction(shortname, messageId);
|
||||||
RocketChat.setReaction(shortname, actionMessage._id);
|
this.onReactionClose();
|
||||||
return toggleReactionPicker();
|
|
||||||
}
|
|
||||||
RocketChat.setReaction(shortname, messageId);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
@ -341,45 +428,49 @@ class RoomView extends React.Component {
|
||||||
}, 1000, true)
|
}, 1000, true)
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
updateUnreadCount = debounce(() => {
|
updateUnreadCount = async() => {
|
||||||
|
const db = database.active;
|
||||||
|
const observable = await db.collections
|
||||||
|
.get('subscriptions')
|
||||||
|
.query(
|
||||||
|
Q.where('archived', false),
|
||||||
|
Q.where('open', true),
|
||||||
|
Q.where('rid', Q.notEq(this.rid))
|
||||||
|
)
|
||||||
|
.observeWithColumns(['unread']);
|
||||||
|
|
||||||
|
this.queryUnreads = observable.subscribe((data) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
const unreadsCount = this.chats.filtered('archived != true && open == true && unread > 0').reduce((a, b) => a + (b.unread || 0), 0);
|
const unreadsCount = data.filter(s => s.unread > 0).reduce((a, b) => a + (b.unread || 0), 0);
|
||||||
if (unreadsCount !== navigation.getParam('unreadsCount')) {
|
if (unreadsCount !== navigation.getParam('unreadsCount')) {
|
||||||
navigation.setParams({
|
navigation.setParams({
|
||||||
unreadsCount
|
unreadsCount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 300, false)
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onThreadPress = debounce((item) => {
|
onThreadPress = debounce(async(item) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
if (item.tmid) {
|
if (item.tmid) {
|
||||||
|
if (!item.tmsg) {
|
||||||
|
await this.fetchThreadName(item.tmid, item.id);
|
||||||
|
}
|
||||||
navigation.push('RoomView', {
|
navigation.push('RoomView', {
|
||||||
rid: item.rid, tmid: item.tmid, name: item.tmsg, t: 'thread'
|
rid: item.subscription.id, tmid: item.tmid, name: item.tmsg, t: 'thread'
|
||||||
});
|
});
|
||||||
} else if (item.tlm) {
|
} else if (item.tlm) {
|
||||||
const title = item.msg || (item.attachments && item.attachments.length && item.attachments[0].title);
|
|
||||||
navigation.push('RoomView', {
|
navigation.push('RoomView', {
|
||||||
rid: item.rid, tmid: item._id, name: title, t: 'thread'
|
rid: item.subscription.id, tmid: item.id, name: item.msg, t: 'thread'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 1000, true)
|
}, 1000, true)
|
||||||
|
|
||||||
toggleReactionPicker = (message) => {
|
|
||||||
const { toggleReactionPicker } = this.props;
|
|
||||||
toggleReactionPicker(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
replyBroadcast = (message) => {
|
replyBroadcast = (message) => {
|
||||||
const { replyBroadcast } = this.props;
|
const { replyBroadcast } = this.props;
|
||||||
replyBroadcast(message);
|
replyBroadcast(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
errorActionsShow = (message) => {
|
|
||||||
const { errorActionsShow } = this.props;
|
|
||||||
errorActionsShow(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleConnected = () => {
|
handleConnected = () => {
|
||||||
this.init();
|
this.init();
|
||||||
EventEmitter.removeListener('connected', this.handleConnected);
|
EventEmitter.removeListener('connected', this.handleConnected);
|
||||||
|
@ -395,15 +486,6 @@ class RoomView extends React.Component {
|
||||||
this.setState(...args);
|
this.setState(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRoom = () => {
|
|
||||||
this.updateStateInteraction = InteractionManager.runAfterInteractions(() => {
|
|
||||||
if (this.rooms[0]) {
|
|
||||||
const room = JSON.parse(JSON.stringify(this.rooms[0] || {}));
|
|
||||||
this.internalSetState({ room });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMessage = (message, tmid) => {
|
sendMessage = (message, tmid) => {
|
||||||
const { user } = this.props;
|
const { user } = this.props;
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.easeInEaseOut();
|
||||||
|
@ -433,12 +515,21 @@ class RoomView extends React.Component {
|
||||||
|
|
||||||
getThreadMessages = () => {
|
getThreadMessages = () => {
|
||||||
try {
|
try {
|
||||||
return RocketChat.loadThreadMessages({ tmid: this.tmid });
|
return RocketChat.loadThreadMessages({ tmid: this.tmid, rid: this.rid });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCustomEmoji = (name) => {
|
||||||
|
const { customEmojis } = this.props;
|
||||||
|
const emoji = customEmojis[name];
|
||||||
|
if (emoji) {
|
||||||
|
return emoji;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
setLastOpen = lastOpen => this.setState({ lastOpen });
|
setLastOpen = lastOpen => this.setState({ lastOpen });
|
||||||
|
|
||||||
joinRoom = async() => {
|
joinRoom = async() => {
|
||||||
|
@ -450,16 +541,43 @@ class RoomView extends React.Component {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
fetchThreadName = async(tmid) => {
|
fetchThreadName = async(tmid, messageId) => {
|
||||||
try {
|
try {
|
||||||
// TODO: we should build a tmid queue here in order to search for a single tmid only once
|
const { room } = this.state;
|
||||||
const thread = await RocketChat.getSingleMessage(tmid);
|
const db = database.active;
|
||||||
database.write(() => {
|
const threadCollection = db.collections.get('threads');
|
||||||
database.create('threads', buildMessage(EJSON.fromJSONValue(thread)), true);
|
const messageCollection = db.collections.get('messages');
|
||||||
|
const messageRecord = await messageCollection.find(messageId);
|
||||||
|
let threadRecord;
|
||||||
|
try {
|
||||||
|
threadRecord = await threadCollection.find(tmid);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Thread not found. We have to search for it.');
|
||||||
|
}
|
||||||
|
if (threadRecord) {
|
||||||
|
await db.action(async() => {
|
||||||
|
await messageRecord.update((m) => {
|
||||||
|
m.tmsg = threadRecord.msg || (threadRecord.attachments && threadRecord.attachments.length && threadRecord.attachments[0].title);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const thread = await RocketChat.getSingleMessage(tmid);
|
||||||
|
await db.action(async() => {
|
||||||
|
await db.batch(
|
||||||
|
threadCollection.prepareCreate((t) => {
|
||||||
|
t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
|
||||||
|
t.subscription.set(room);
|
||||||
|
Object.assign(t, thread);
|
||||||
|
}),
|
||||||
|
messageRecord.prepareUpdate((m) => {
|
||||||
|
m.tmsg = thread.msg || (thread.attachments && thread.attachments.length && thread.attachments[0].title);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
@ -482,6 +600,12 @@ class RoomView extends React.Component {
|
||||||
navigation.navigate('RoomInfoView', navParam);
|
navigation.navigate('RoomInfoView', navParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isReadOnly() {
|
||||||
|
const { room } = this.state;
|
||||||
|
const { user } = this.props;
|
||||||
|
return isReadOnly(room, user);
|
||||||
|
}
|
||||||
|
|
||||||
renderItem = (item, previousItem) => {
|
renderItem = (item, previousItem) => {
|
||||||
const { room, lastOpen, canAutoTranslate } = this.state;
|
const { room, lastOpen, canAutoTranslate } = this.state;
|
||||||
const {
|
const {
|
||||||
|
@ -504,13 +628,12 @@ class RoomView extends React.Component {
|
||||||
|
|
||||||
const message = (
|
const message = (
|
||||||
<Message
|
<Message
|
||||||
key={item._id}
|
|
||||||
item={item}
|
item={item}
|
||||||
user={user}
|
user={user}
|
||||||
archived={room.archived}
|
archived={room.archived}
|
||||||
broadcast={room.broadcast}
|
broadcast={room.broadcast}
|
||||||
status={item.status}
|
status={item.status}
|
||||||
_updatedAt={item._updatedAt}
|
isThreadRoom={!!this.tmid}
|
||||||
previousItem={previousItem}
|
previousItem={previousItem}
|
||||||
fetchThreadName={this.fetchThreadName}
|
fetchThreadName={this.fetchThreadName}
|
||||||
onReactionPress={this.onReactionPress}
|
onReactionPress={this.onReactionPress}
|
||||||
|
@ -519,7 +642,7 @@ class RoomView extends React.Component {
|
||||||
onDiscussionPress={this.onDiscussionPress}
|
onDiscussionPress={this.onDiscussionPress}
|
||||||
onThreadPress={this.onThreadPress}
|
onThreadPress={this.onThreadPress}
|
||||||
onOpenFileModal={this.onOpenFileModal}
|
onOpenFileModal={this.onOpenFileModal}
|
||||||
toggleReactionPicker={this.toggleReactionPicker}
|
reactionInit={this.onReactionInit}
|
||||||
replyBroadcast={this.replyBroadcast}
|
replyBroadcast={this.replyBroadcast}
|
||||||
errorActionsShow={this.errorActionsShow}
|
errorActionsShow={this.errorActionsShow}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
|
@ -531,6 +654,7 @@ class RoomView extends React.Component {
|
||||||
autoTranslateRoom={canAutoTranslate && room.autoTranslate}
|
autoTranslateRoom={canAutoTranslate && room.autoTranslate}
|
||||||
autoTranslateLanguage={room.autoTranslateLanguage}
|
autoTranslateLanguage={room.autoTranslateLanguage}
|
||||||
navToRoomInfo={this.navToRoomInfo}
|
navToRoomInfo={this.navToRoomInfo}
|
||||||
|
getCustomEmoji={this.getCustomEmoji}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -550,8 +674,10 @@ class RoomView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFooter = () => {
|
renderFooter = () => {
|
||||||
const { joined, room } = this.state;
|
const {
|
||||||
const { navigation, user } = this.props;
|
joined, room, selectedMessage, editing, replying, replyWithMention
|
||||||
|
} = this.state;
|
||||||
|
const { navigation } = this.props;
|
||||||
|
|
||||||
if (!joined && !this.tmid) {
|
if (!joined && !this.tmid) {
|
||||||
return (
|
return (
|
||||||
|
@ -568,7 +694,7 @@ class RoomView extends React.Component {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isReadOnly(room, user)) {
|
if (this.isReadOnly) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.readOnly}>
|
<View style={styles.readOnly}>
|
||||||
<Text style={styles.previewMode}>{I18n.t('This_room_is_read_only')}</Text>
|
<Text style={styles.previewMode}>{I18n.t('This_room_is_read_only')}</Text>
|
||||||
|
@ -590,33 +716,60 @@ class RoomView extends React.Component {
|
||||||
tmid={this.tmid}
|
tmid={this.tmid}
|
||||||
roomType={room.t}
|
roomType={room.t}
|
||||||
isFocused={navigation.isFocused()}
|
isFocused={navigation.isFocused()}
|
||||||
|
message={selectedMessage}
|
||||||
|
editing={editing}
|
||||||
|
editRequest={this.onEditRequest}
|
||||||
|
editCancel={this.onEditCancel}
|
||||||
|
replying={replying}
|
||||||
|
replyWithMention={replyWithMention}
|
||||||
|
replyCancel={this.onReplyCancel}
|
||||||
|
getCustomEmoji={this.getCustomEmoji}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderActions = () => {
|
renderActions = () => {
|
||||||
const { room } = this.state;
|
|
||||||
const {
|
const {
|
||||||
user, showActions, showErrorActions, navigation
|
room, selectedMessage, showActions, showErrorActions, joined
|
||||||
|
} = this.state;
|
||||||
|
const {
|
||||||
|
user, navigation
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (!navigation.isFocused()) {
|
if (!navigation.isFocused()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<>
|
||||||
{room._id && showActions
|
{joined && showActions
|
||||||
? <MessageActions room={room} tmid={this.tmid} user={user} />
|
? (
|
||||||
|
<MessageActions
|
||||||
|
tmid={this.tmid}
|
||||||
|
room={room}
|
||||||
|
user={user}
|
||||||
|
message={selectedMessage}
|
||||||
|
actionsHide={this.onActionsHide}
|
||||||
|
editInit={this.onEditInit}
|
||||||
|
replyInit={this.onReplyInit}
|
||||||
|
reactionInit={this.onReactionInit}
|
||||||
|
isReadOnly={this.isReadOnly}
|
||||||
|
/>
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
{showErrorActions ? <MessageErrorActions /> : null}
|
{showErrorActions ? (
|
||||||
</React.Fragment>
|
<MessageErrorActions
|
||||||
|
message={selectedMessage}
|
||||||
|
actionsHide={this.onErrorActionsHide}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
console.count(`${ this.constructor.name }.render calls`);
|
console.count(`${ this.constructor.name }.render calls`);
|
||||||
const {
|
const {
|
||||||
room, photoModalVisible, reactionsModalVisible, selectedAttachment, selectedMessage
|
room, photoModalVisible, reactionsModalVisible, selectedAttachment, selectedMessage, loading, reacting
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { user, baseUrl } = this.props;
|
const { user, baseUrl } = this.props;
|
||||||
const { rid, t } = room;
|
const { rid, t } = room;
|
||||||
|
@ -624,10 +777,23 @@ class RoomView extends React.Component {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} testID='room-view' forceInset={{ vertical: 'never' }}>
|
<SafeAreaView style={styles.container} testID='room-view' forceInset={{ vertical: 'never' }}>
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
<List rid={rid} t={t} tmid={this.tmid} renderRow={this.renderItem} />
|
<List
|
||||||
|
rid={rid}
|
||||||
|
t={t}
|
||||||
|
tmid={this.tmid}
|
||||||
|
room={room}
|
||||||
|
renderRow={this.renderItem}
|
||||||
|
loading={loading}
|
||||||
|
animated={this.beginAnimating}
|
||||||
|
/>
|
||||||
{this.renderFooter()}
|
{this.renderFooter()}
|
||||||
{this.renderActions()}
|
{this.renderActions()}
|
||||||
<ReactionPicker onEmojiSelected={this.onReactionPress} />
|
<ReactionPicker
|
||||||
|
show={reacting}
|
||||||
|
message={selectedMessage}
|
||||||
|
onEmojiSelected={this.onReactionPress}
|
||||||
|
reactionClose={this.onReactionClose}
|
||||||
|
/>
|
||||||
<UploadProgress rid={this.rid} user={user} baseUrl={baseUrl} />
|
<UploadProgress rid={this.rid} user={user} baseUrl={baseUrl} />
|
||||||
<FileModal
|
<FileModal
|
||||||
attachment={selectedAttachment}
|
attachment={selectedAttachment}
|
||||||
|
@ -639,9 +805,10 @@ class RoomView extends React.Component {
|
||||||
<ReactionsModal
|
<ReactionsModal
|
||||||
message={selectedMessage}
|
message={selectedMessage}
|
||||||
isVisible={reactionsModalVisible}
|
isVisible={reactionsModalVisible}
|
||||||
onClose={this.onCloseReactionsModal}
|
|
||||||
user={user}
|
user={user}
|
||||||
baseUrl={baseUrl}
|
baseUrl={baseUrl}
|
||||||
|
onClose={this.onCloseReactionsModal}
|
||||||
|
getCustomEmoji={this.getCustomEmoji}
|
||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
@ -654,27 +821,18 @@ const mapStateToProps = state => ({
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
},
|
},
|
||||||
actionMessage: state.messages.actionMessage,
|
|
||||||
editing: state.messages.editing,
|
|
||||||
replying: state.messages.replying,
|
|
||||||
showActions: state.messages.showActions,
|
|
||||||
showErrorActions: state.messages.showErrorActions,
|
|
||||||
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background',
|
appState: state.app.ready && state.app.foreground ? 'foreground' : 'background',
|
||||||
useRealName: state.settings.UI_Use_Real_Name,
|
useRealName: state.settings.UI_Use_Real_Name,
|
||||||
isAuthenticated: state.login.isAuthenticated,
|
isAuthenticated: state.login.isAuthenticated,
|
||||||
Message_GroupingPeriod: state.settings.Message_GroupingPeriod,
|
Message_GroupingPeriod: state.settings.Message_GroupingPeriod,
|
||||||
Message_TimeFormat: state.settings.Message_TimeFormat,
|
Message_TimeFormat: state.settings.Message_TimeFormat,
|
||||||
useMarkdown: state.markdown.useMarkdown,
|
useMarkdown: state.markdown.useMarkdown,
|
||||||
|
customEmojis: state.customEmojis,
|
||||||
baseUrl: state.settings.baseUrl || state.server ? state.server.server : '',
|
baseUrl: state.settings.baseUrl || state.server ? state.server.server : '',
|
||||||
Message_Read_Receipt_Enabled: state.settings.Message_Read_Receipt_Enabled
|
Message_Read_Receipt_Enabled: state.settings.Message_Read_Receipt_Enabled
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
editCancel: () => dispatch(editCancelAction()),
|
|
||||||
replyCancel: () => dispatch(replyCancelAction()),
|
|
||||||
toggleReactionPicker: message => dispatch(toggleReactionPickerAction(message)),
|
|
||||||
errorActionsShow: actionMessage => dispatch(errorActionsShowAction(actionMessage)),
|
|
||||||
actionsShow: actionMessage => dispatch(actionsShowAction(actionMessage)),
|
|
||||||
replyBroadcast: message => dispatch(replyBroadcastAction(message))
|
replyBroadcast: message => dispatch(replyBroadcastAction(message))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,12 @@ import { toggleServerDropdown as toggleServerDropdownAction } from '../../action
|
||||||
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
|
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
|
||||||
import { appStart as appStartAction } from '../../actions';
|
import { appStart as appStartAction } from '../../actions';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import EventEmitter from '../../utils/events';
|
import EventEmitter from '../../utils/events';
|
||||||
import Check from '../../containers/Check';
|
import Check from '../../containers/Check';
|
||||||
|
import database from '../../lib/database';
|
||||||
|
|
||||||
const ROW_HEIGHT = 68;
|
const ROW_HEIGHT = 68;
|
||||||
const ANIMATION_DURATION = 200;
|
const ANIMATION_DURATION = 200;
|
||||||
|
@ -34,15 +34,21 @@ class ServerDropdown extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.servers = database.databases.serversDB.objects('servers');
|
this.state = { servers: [] };
|
||||||
this.state = {
|
|
||||||
servers: this.servers
|
|
||||||
};
|
|
||||||
this.animatedValue = new Animated.Value(0);
|
this.animatedValue = new Animated.Value(0);
|
||||||
safeAddListener(this.servers, this.updateState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
async componentDidMount() {
|
||||||
|
const serversDB = database.servers;
|
||||||
|
const observable = await serversDB.collections
|
||||||
|
.get('servers')
|
||||||
|
.query()
|
||||||
|
.observeWithColumns(['name']);
|
||||||
|
|
||||||
|
this.subscription = observable.subscribe((data) => {
|
||||||
|
this.setState({ servers: data });
|
||||||
|
});
|
||||||
|
|
||||||
Animated.timing(
|
Animated.timing(
|
||||||
this.animatedValue,
|
this.animatedValue,
|
||||||
{
|
{
|
||||||
|
@ -81,11 +87,9 @@ class ServerDropdown extends Component {
|
||||||
clearTimeout(this.newServerTimeout);
|
clearTimeout(this.newServerTimeout);
|
||||||
this.newServerTimeout = false;
|
this.newServerTimeout = false;
|
||||||
}
|
}
|
||||||
|
if (this.subscription && this.subscription.unsubscribe) {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateState = () => {
|
|
||||||
const { servers } = this;
|
|
||||||
this.setState({ servers });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
close = () => {
|
close = () => {
|
||||||
|
|
|
@ -1,14 +1,23 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
View, FlatList, BackHandler, ActivityIndicator, Text, ScrollView, Keyboard, LayoutAnimation, InteractionManager, Dimensions
|
View,
|
||||||
|
FlatList,
|
||||||
|
BackHandler,
|
||||||
|
ActivityIndicator,
|
||||||
|
Text,
|
||||||
|
ScrollView,
|
||||||
|
Keyboard,
|
||||||
|
LayoutAnimation,
|
||||||
|
Dimensions
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual, orderBy } from 'lodash';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
import Orientation from 'react-native-orientation-locker';
|
import Orientation from 'react-native-orientation-locker';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
import database, { safeAddListener } from '../../lib/realm';
|
import database from '../../lib/database';
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import RoomItem, { ROW_HEIGHT } from '../../presentation/RoomItem';
|
import RoomItem, { ROW_HEIGHT } from '../../presentation/RoomItem';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -26,47 +35,87 @@ import { appStart as appStartAction } from '../../actions';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import { isIOS, isAndroid } from '../../utils/deviceInfo';
|
import { isIOS, isAndroid } from '../../utils/deviceInfo';
|
||||||
import RoomsListHeaderView from './Header';
|
import RoomsListHeaderView from './Header';
|
||||||
import { DrawerButton, CustomHeaderButtons, Item } from '../../containers/HeaderButton';
|
import {
|
||||||
|
DrawerButton,
|
||||||
|
CustomHeaderButtons,
|
||||||
|
Item
|
||||||
|
} from '../../containers/HeaderButton';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import ListHeader from './ListHeader';
|
import ListHeader from './ListHeader';
|
||||||
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
|
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
|
||||||
|
|
||||||
const SCROLL_OFFSET = 56;
|
const SCROLL_OFFSET = 56;
|
||||||
|
|
||||||
const shouldUpdateProps = ['searchText', 'loadingServer', 'showServerDropdown', 'showSortDropdown', 'sortBy', 'groupByType', 'showFavorites', 'showUnread', 'useRealName', 'StoreLastMessage', 'appState'];
|
const shouldUpdateProps = [
|
||||||
const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index });
|
'searchText',
|
||||||
|
'loadingServer',
|
||||||
|
'showServerDropdown',
|
||||||
|
'showSortDropdown',
|
||||||
|
'sortBy',
|
||||||
|
'groupByType',
|
||||||
|
'showFavorites',
|
||||||
|
'showUnread',
|
||||||
|
'useRealName',
|
||||||
|
'StoreLastMessage',
|
||||||
|
'appState',
|
||||||
|
'isAuthenticated'
|
||||||
|
];
|
||||||
|
const getItemLayout = (data, index) => ({
|
||||||
|
length: ROW_HEIGHT,
|
||||||
|
offset: ROW_HEIGHT * index,
|
||||||
|
index
|
||||||
|
});
|
||||||
const keyExtractor = item => item.rid;
|
const keyExtractor = item => item.rid;
|
||||||
|
|
||||||
class RoomsListView extends React.Component {
|
class RoomsListView extends React.Component {
|
||||||
static navigationOptions = ({ navigation }) => {
|
static navigationOptions = ({ navigation }) => {
|
||||||
const searching = navigation.getParam('searching');
|
const searching = navigation.getParam('searching');
|
||||||
const cancelSearchingAndroid = navigation.getParam('cancelSearchingAndroid');
|
const cancelSearchingAndroid = navigation.getParam(
|
||||||
|
'cancelSearchingAndroid'
|
||||||
|
);
|
||||||
const onPressItem = navigation.getParam('onPressItem', () => {});
|
const onPressItem = navigation.getParam('onPressItem', () => {});
|
||||||
const initSearchingAndroid = navigation.getParam('initSearchingAndroid', () => {});
|
const initSearchingAndroid = navigation.getParam(
|
||||||
|
'initSearchingAndroid',
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
headerLeft: (
|
headerLeft: searching ? (
|
||||||
searching
|
|
||||||
? (
|
|
||||||
<CustomHeaderButtons left>
|
<CustomHeaderButtons left>
|
||||||
<Item title='cancel' iconName='cross' onPress={cancelSearchingAndroid} />
|
<Item
|
||||||
|
title='cancel'
|
||||||
|
iconName='cross'
|
||||||
|
onPress={cancelSearchingAndroid}
|
||||||
|
/>
|
||||||
</CustomHeaderButtons>
|
</CustomHeaderButtons>
|
||||||
)
|
) : (
|
||||||
: <DrawerButton navigation={navigation} testID='rooms-list-view-sidebar' />
|
<DrawerButton
|
||||||
|
navigation={navigation}
|
||||||
|
testID='rooms-list-view-sidebar'
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
headerTitle: <RoomsListHeaderView />,
|
headerTitle: <RoomsListHeaderView />,
|
||||||
headerRight: (
|
headerRight: searching ? null : (
|
||||||
searching
|
|
||||||
? null
|
|
||||||
: (
|
|
||||||
<CustomHeaderButtons>
|
<CustomHeaderButtons>
|
||||||
{isAndroid ? <Item title='search' iconName='magnifier' onPress={initSearchingAndroid} /> : null}
|
{isAndroid ? (
|
||||||
<Item title='new' iconName='edit-rounded' onPress={() => navigation.navigate('NewMessageView', { onPressItem })} testID='rooms-list-view-create-channel' />
|
<Item
|
||||||
|
title='search'
|
||||||
|
iconName='magnifier'
|
||||||
|
onPress={initSearchingAndroid}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<Item
|
||||||
|
title='new'
|
||||||
|
iconName='edit-rounded'
|
||||||
|
onPress={() => navigation.navigate('NewMessageView', {
|
||||||
|
onPressItem
|
||||||
|
})}
|
||||||
|
testID='rooms-list-view-create-channel'
|
||||||
|
/>
|
||||||
</CustomHeaderButtons>
|
</CustomHeaderButtons>
|
||||||
)
|
)
|
||||||
)
|
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object,
|
navigation: PropTypes.object,
|
||||||
|
@ -92,7 +141,7 @@ class RoomsListView extends React.Component {
|
||||||
appStart: PropTypes.func,
|
appStart: PropTypes.func,
|
||||||
roomsRequest: PropTypes.func,
|
roomsRequest: PropTypes.func,
|
||||||
isAuthenticated: PropTypes.bool
|
isAuthenticated: PropTypes.bool
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -100,11 +149,11 @@ class RoomsListView extends React.Component {
|
||||||
console.time(`${ this.constructor.name } mount`);
|
console.time(`${ this.constructor.name } mount`);
|
||||||
|
|
||||||
const { width } = Dimensions.get('window');
|
const { width } = Dimensions.get('window');
|
||||||
this.data = [];
|
|
||||||
this.state = {
|
this.state = {
|
||||||
searching: false,
|
searching: false,
|
||||||
search: [],
|
search: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
|
allChats: [],
|
||||||
chats: [],
|
chats: [],
|
||||||
unread: [],
|
unread: [],
|
||||||
favorites: [],
|
favorites: [],
|
||||||
|
@ -112,12 +161,20 @@ class RoomsListView extends React.Component {
|
||||||
channels: [],
|
channels: [],
|
||||||
privateGroup: [],
|
privateGroup: [],
|
||||||
direct: [],
|
direct: [],
|
||||||
livechat: [],
|
|
||||||
width
|
width
|
||||||
};
|
};
|
||||||
Orientation.unlockAllOrientations();
|
Orientation.unlockAllOrientations();
|
||||||
this.didFocusListener = props.navigation.addListener('didFocus', () => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress));
|
this.didFocusListener = props.navigation.addListener('didFocus', () => {
|
||||||
this.willBlurListener = props.navigation.addListener('willBlur', () => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress));
|
BackHandler.addEventListener(
|
||||||
|
'hardwareBackPress',
|
||||||
|
this.handleBackPress
|
||||||
|
);
|
||||||
|
this.forceUpdate();
|
||||||
|
});
|
||||||
|
this.willBlurListener = props.navigation.addListener('willBlur', () => BackHandler.addEventListener(
|
||||||
|
'hardwareBackPress',
|
||||||
|
this.handleBackPress
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -153,51 +210,70 @@ class RoomsListView extends React.Component {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { loading, searching, width } = this.state;
|
if (!nextProps.navigation.isFocused()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
loading,
|
||||||
|
searching,
|
||||||
|
width,
|
||||||
|
allChats,
|
||||||
|
search
|
||||||
|
} = this.state;
|
||||||
if (nextState.loading !== loading) {
|
if (nextState.loading !== loading) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (nextState.searching !== searching) {
|
if (nextState.searching !== searching) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextState.width !== width) {
|
if (nextState.width !== width) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { search } = this.state;
|
|
||||||
if (!isEqual(nextState.search, search)) {
|
if (!isEqual(nextState.search, search)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!isEqual(nextState.allChats, allChats)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const {
|
const {
|
||||||
sortBy, groupByType, showFavorites, showUnread, appState, roomsRequest, isAuthenticated
|
sortBy,
|
||||||
|
groupByType,
|
||||||
|
showFavorites,
|
||||||
|
showUnread,
|
||||||
|
appState,
|
||||||
|
roomsRequest,
|
||||||
|
isAuthenticated
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!(
|
if (
|
||||||
(prevProps.sortBy === sortBy)
|
!(
|
||||||
&& (prevProps.groupByType === groupByType)
|
prevProps.sortBy === sortBy
|
||||||
&& (prevProps.showFavorites === showFavorites)
|
&& prevProps.groupByType === groupByType
|
||||||
&& (prevProps.showUnread === showUnread)
|
&& prevProps.showFavorites === showFavorites
|
||||||
)) {
|
&& prevProps.showUnread === showUnread
|
||||||
|
)
|
||||||
|
) {
|
||||||
this.getSubscriptions();
|
this.getSubscriptions();
|
||||||
} else if (appState === 'foreground' && appState !== prevProps.appState && isAuthenticated) {
|
} else if (
|
||||||
|
appState === 'foreground'
|
||||||
|
&& appState !== prevProps.appState
|
||||||
|
&& isAuthenticated
|
||||||
|
) {
|
||||||
roomsRequest();
|
roomsRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.data && this.data.removeAllListeners) {
|
|
||||||
this.data.removeAllListeners();
|
|
||||||
}
|
|
||||||
if (this.getSubscriptions && this.getSubscriptions.stop) {
|
if (this.getSubscriptions && this.getSubscriptions.stop) {
|
||||||
this.getSubscriptions.stop();
|
this.getSubscriptions.stop();
|
||||||
}
|
}
|
||||||
if (this.updateStateInteraction && this.updateStateInteraction.cancel) {
|
if (this.querySubscription && this.querySubscription.unsubscribe) {
|
||||||
this.updateStateInteraction.cancel();
|
this.querySubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
if (this.didFocusListener && this.didFocusListener.remove) {
|
if (this.didFocusListener && this.didFocusListener.remove) {
|
||||||
this.didFocusListener.remove();
|
this.didFocusListener.remove();
|
||||||
|
@ -209,7 +285,7 @@ class RoomsListView extends React.Component {
|
||||||
console.countReset(`${ this.constructor.name }.render calls`);
|
console.countReset(`${ this.constructor.name }.render calls`);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDimensionsChange = ({ window: { width } }) => this.setState({ width })
|
onDimensionsChange = ({ window: { width } }) => this.setState({ width });
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
// eslint-disable-next-line react/sort-comp
|
||||||
internalSetState = (...args) => {
|
internalSetState = (...args) => {
|
||||||
|
@ -218,77 +294,104 @@ class RoomsListView extends React.Component {
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.easeInEaseOut();
|
||||||
}
|
}
|
||||||
this.setState(...args);
|
this.setState(...args);
|
||||||
}
|
};
|
||||||
|
|
||||||
getSubscriptions = debounce(() => {
|
getSubscriptions = debounce(async() => {
|
||||||
if (this.data && this.data.removeAllListeners) {
|
if (this.querySubscription && this.querySubscription.unsubscribe) {
|
||||||
this.data.removeAllListeners();
|
this.querySubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
server, sortBy, showUnread, showFavorites, groupByType
|
sortBy,
|
||||||
|
showUnread,
|
||||||
|
showFavorites,
|
||||||
|
groupByType
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (server && this.hasActiveDB()) {
|
const db = database.active;
|
||||||
this.data = database.objects('subscriptions').filtered('archived != true && open == true && t != $0', 'l');
|
const observable = await db.collections
|
||||||
|
.get('subscriptions')
|
||||||
|
.query(
|
||||||
|
Q.where('archived', false),
|
||||||
|
Q.where('open', true),
|
||||||
|
Q.where('t', Q.notEq('l'))
|
||||||
|
)
|
||||||
|
.observeWithColumns(['room_updated_at', 'unread', 'alert', 'user_mentions', 'f', 't']);
|
||||||
|
|
||||||
|
this.querySubscription = observable.subscribe((data) => {
|
||||||
|
let chats = [];
|
||||||
|
let unread = [];
|
||||||
|
let favorites = [];
|
||||||
|
let discussions = [];
|
||||||
|
let channels = [];
|
||||||
|
let privateGroup = [];
|
||||||
|
let direct = [];
|
||||||
if (sortBy === 'alphabetical') {
|
if (sortBy === 'alphabetical') {
|
||||||
this.data = this.data.sorted('name', false);
|
chats = orderBy(data, ['name'], ['asc']);
|
||||||
} else {
|
} else {
|
||||||
this.data = this.data.sorted('roomUpdatedAt', true);
|
chats = orderBy(data, ['roomUpdatedAt'], ['desc']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// it's better to map and test all subs altogether then testing them individually
|
||||||
|
const allChats = data.map(item => ({
|
||||||
|
alert: item.alert,
|
||||||
|
unread: item.unread,
|
||||||
|
userMentions: item.userMentions,
|
||||||
|
isRead: this.getIsRead(item),
|
||||||
|
favorite: item.f,
|
||||||
|
lastMessage: item.lastMessage,
|
||||||
|
name: this.getRoomTitle(item),
|
||||||
|
_updatedAt: item.roomUpdatedAt,
|
||||||
|
key: item._id,
|
||||||
|
rid: item.rid,
|
||||||
|
type: item.t,
|
||||||
|
prid: item.prid
|
||||||
|
}));
|
||||||
|
|
||||||
// unread
|
// unread
|
||||||
if (showUnread) {
|
if (showUnread) {
|
||||||
this.unread = this.data.filtered('(unread > 0 || alert == true)');
|
unread = chats.filter(s => s.unread > 0 || s.alert);
|
||||||
} else {
|
} else {
|
||||||
this.unread = [];
|
unread = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// favorites
|
// favorites
|
||||||
if (showFavorites) {
|
if (showFavorites) {
|
||||||
this.favorites = this.data.filtered('f == true');
|
favorites = chats.filter(s => s.f);
|
||||||
} else {
|
} else {
|
||||||
this.favorites = [];
|
favorites = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// type
|
// type
|
||||||
if (groupByType) {
|
if (groupByType) {
|
||||||
this.discussions = this.data.filtered('prid != null');
|
discussions = chats.filter(s => s.prid);
|
||||||
this.channels = this.data.filtered('t == $0 AND prid == null', 'c');
|
channels = chats.filter(s => s.t === 'c' && !s.prid);
|
||||||
this.privateGroup = this.data.filtered('t == $0 AND prid == null', 'p');
|
privateGroup = chats.filter(s => s.t === 'p' && !s.prid);
|
||||||
this.direct = this.data.filtered('t == $0 AND prid == null', 'd');
|
direct = chats.filter(s => s.t === 'd' && !s.prid);
|
||||||
this.livechat = this.data.filtered('t == $0 AND prid == null', 'l');
|
|
||||||
} else if (showUnread) {
|
} else if (showUnread) {
|
||||||
this.chats = this.data.filtered('(unread == 0 && alert == false)');
|
chats = chats.filter(s => !s.unread && !s.alert);
|
||||||
} else {
|
|
||||||
this.chats = this.data;
|
|
||||||
}
|
}
|
||||||
safeAddListener(this.data, this.updateState);
|
|
||||||
}
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
|
||||||
updateState = debounce(() => {
|
|
||||||
this.updateStateInteraction = InteractionManager.runAfterInteractions(() => {
|
|
||||||
this.internalSetState({
|
this.internalSetState({
|
||||||
chats: this.chats ? this.chats.slice() : [],
|
allChats,
|
||||||
unread: this.unread ? this.unread.slice() : [],
|
chats,
|
||||||
favorites: this.favorites ? this.favorites.slice() : [],
|
unread,
|
||||||
discussions: this.discussions ? this.discussions.slice() : [],
|
favorites,
|
||||||
channels: this.channels ? this.channels.slice() : [],
|
discussions,
|
||||||
privateGroup: this.privateGroup ? this.privateGroup.slice() : [],
|
channels,
|
||||||
direct: this.direct ? this.direct.slice() : [],
|
privateGroup,
|
||||||
livechat: this.livechat ? this.livechat.slice() : [],
|
direct,
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
this.forceUpdate();
|
|
||||||
});
|
});
|
||||||
}, 300);
|
}, 300, true);
|
||||||
|
|
||||||
initSearchingAndroid = () => {
|
initSearchingAndroid = () => {
|
||||||
const { openSearchHeader, navigation } = this.props;
|
const { openSearchHeader, navigation } = this.props;
|
||||||
this.setState({ searching: true });
|
this.setState({ searching: true });
|
||||||
navigation.setParams({ searching: true });
|
navigation.setParams({ searching: true });
|
||||||
openSearchHeader();
|
openSearchHeader();
|
||||||
}
|
};
|
||||||
|
|
||||||
cancelSearchingAndroid = () => {
|
cancelSearchingAndroid = () => {
|
||||||
if (isAndroid) {
|
if (isAndroid) {
|
||||||
|
@ -299,10 +402,7 @@ class RoomsListView extends React.Component {
|
||||||
this.internalSetState({ search: [] });
|
this.internalSetState({ search: [] });
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// this is necessary during development (enables Cmd + r)
|
|
||||||
hasActiveDB = () => database && database.databases && database.databases.activeDB;
|
|
||||||
|
|
||||||
handleBackPress = () => {
|
handleBackPress = () => {
|
||||||
const { searching } = this.state;
|
const { searching } = this.state;
|
||||||
|
@ -313,29 +413,32 @@ class RoomsListView extends React.Component {
|
||||||
}
|
}
|
||||||
appStart('background');
|
appStart('background');
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
_isUnread = item => item.unread > 0 || item.alert
|
// eslint-disable-next-line react/sort-comp
|
||||||
|
search = debounce(async(text) => {
|
||||||
search = async(text) => {
|
|
||||||
const result = await RocketChat.search({ text });
|
const result = await RocketChat.search({ text });
|
||||||
this.internalSetState({
|
this.internalSetState({
|
||||||
search: result
|
search: result
|
||||||
});
|
});
|
||||||
}
|
}, 300);
|
||||||
|
|
||||||
getRoomTitle = (item) => {
|
getRoomTitle = (item) => {
|
||||||
const { useRealName } = this.props;
|
const { useRealName } = this.props;
|
||||||
return ((item.prid || useRealName) && item.fname) || item.name;
|
return ((item.prid || useRealName) && item.fname) || item.name;
|
||||||
}
|
};
|
||||||
|
|
||||||
goRoom = (item) => {
|
goRoom = (item) => {
|
||||||
this.cancelSearchingAndroid();
|
this.cancelSearchingAndroid();
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
navigation.navigate('RoomView', {
|
navigation.navigate('RoomView', {
|
||||||
rid: item.rid, name: this.getRoomTitle(item), t: item.t, prid: item.prid
|
rid: item.rid,
|
||||||
|
name: this.getRoomTitle(item),
|
||||||
|
t: item.t,
|
||||||
|
prid: item.prid,
|
||||||
|
room: item
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_onPressItem = async(item = {}) => {
|
_onPressItem = async(item = {}) => {
|
||||||
if (!item.search) {
|
if (!item.search) {
|
||||||
|
@ -347,7 +450,11 @@ class RoomsListView extends React.Component {
|
||||||
const { username } = item;
|
const { username } = item;
|
||||||
const result = await RocketChat.createDirectMessage(username);
|
const result = await RocketChat.createDirectMessage(username);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return this.goRoom({ rid: result.room._id, name: username, t: 'd' });
|
return this.goRoom({
|
||||||
|
rid: result.room._id,
|
||||||
|
name: username,
|
||||||
|
t: 'd'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
|
@ -355,7 +462,7 @@ class RoomsListView extends React.Component {
|
||||||
} else {
|
} else {
|
||||||
return this.goRoom(item);
|
return this.goRoom(item);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
toggleSort = () => {
|
toggleSort = () => {
|
||||||
const { toggleSortDropdown } = this.props;
|
const { toggleSortDropdown } = this.props;
|
||||||
|
@ -369,60 +476,78 @@ class RoomsListView extends React.Component {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toggleSortDropdown();
|
toggleSortDropdown();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
};
|
||||||
|
|
||||||
toggleFav = async(rid, favorite) => {
|
toggleFav = async(rid, favorite) => {
|
||||||
try {
|
try {
|
||||||
|
const db = database.active;
|
||||||
const result = await RocketChat.toggleFavorite(rid, !favorite);
|
const result = await RocketChat.toggleFavorite(rid, !favorite);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
database.write(() => {
|
const subCollection = db.collections.get('subscriptions');
|
||||||
const sub = database.objects('subscriptions').filtered('rid == $0', rid)[0];
|
await db.action(async() => {
|
||||||
if (sub) {
|
try {
|
||||||
|
const subRecord = await subCollection.find(rid);
|
||||||
|
await subRecord.update((sub) => {
|
||||||
sub.f = !favorite;
|
sub.f = !favorite;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
toggleRead = async(rid, isRead) => {
|
toggleRead = async(rid, isRead) => {
|
||||||
try {
|
try {
|
||||||
|
const db = database.active;
|
||||||
const result = await RocketChat.toggleRead(isRead, rid);
|
const result = await RocketChat.toggleRead(isRead, rid);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
database.write(() => {
|
const subCollection = db.collections.get('subscriptions');
|
||||||
const sub = database.objects('subscriptions').filtered('rid == $0', rid)[0];
|
await db.action(async() => {
|
||||||
if (sub) {
|
try {
|
||||||
|
const subRecord = await subCollection.find(rid);
|
||||||
|
await subRecord.update((sub) => {
|
||||||
sub.alert = isRead;
|
sub.alert = isRead;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
hideChannel = async(rid, type) => {
|
hideChannel = async(rid, type) => {
|
||||||
try {
|
try {
|
||||||
|
const db = database.active;
|
||||||
const result = await RocketChat.hideRoom(rid, type);
|
const result = await RocketChat.hideRoom(rid, type);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
database.write(() => {
|
const subCollection = db.collections.get('subscriptions');
|
||||||
const sub = database.objects('subscriptions').filtered('rid == $0', rid)[0];
|
await db.action(async() => {
|
||||||
database.delete(sub);
|
try {
|
||||||
|
const subRecord = await subCollection.find(rid);
|
||||||
|
await subRecord.destroyPermanently();
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
goDirectory = () => {
|
goDirectory = () => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
navigation.navigate('DirectoryView');
|
navigation.navigate('DirectoryView');
|
||||||
}
|
};
|
||||||
|
|
||||||
getScrollRef = ref => this.scroll = ref
|
getScrollRef = ref => (this.scroll = ref);
|
||||||
|
|
||||||
renderListHeader = () => {
|
renderListHeader = () => {
|
||||||
const { search } = this.state;
|
const { search } = this.state;
|
||||||
|
@ -436,22 +561,25 @@ class RoomsListView extends React.Component {
|
||||||
goDirectory={this.goDirectory}
|
goDirectory={this.goDirectory}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
getIsRead = (item) => {
|
getIsRead = (item) => {
|
||||||
let isUnread = (item.archived !== true && item.open === true); // item is not archived and not opened
|
let isUnread = item.archived !== true && item.open === true; // item is not archived and not opened
|
||||||
isUnread = isUnread && (item.unread > 0 || item.alert === true); // either its unread count > 0 or its alert
|
isUnread = isUnread && (item.unread > 0 || item.alert === true); // either its unread count > 0 or its alert
|
||||||
return !isUnread;
|
return !isUnread;
|
||||||
}
|
};
|
||||||
|
|
||||||
renderItem = ({ item }) => {
|
renderItem = ({ item }) => {
|
||||||
const { width } = this.state;
|
const { width } = this.state;
|
||||||
const {
|
const {
|
||||||
userId, username, token, baseUrl, StoreLastMessage
|
userId,
|
||||||
|
username,
|
||||||
|
token,
|
||||||
|
baseUrl,
|
||||||
|
StoreLastMessage
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const id = item.rid.replace(userId, '').trim();
|
const id = item.rid.replace(userId, '').trim();
|
||||||
|
|
||||||
if (item.search || (item.isValid && item.isValid())) {
|
|
||||||
return (
|
return (
|
||||||
<RoomItem
|
<RoomItem
|
||||||
alert={item.alert}
|
alert={item.alert}
|
||||||
|
@ -459,7 +587,7 @@ class RoomsListView extends React.Component {
|
||||||
userMentions={item.userMentions}
|
userMentions={item.userMentions}
|
||||||
isRead={this.getIsRead(item)}
|
isRead={this.getIsRead(item)}
|
||||||
favorite={item.f}
|
favorite={item.f}
|
||||||
lastMessage={item.lastMessage ? JSON.parse(JSON.stringify(item.lastMessage)) : null}
|
lastMessage={item.lastMessage}
|
||||||
name={this.getRoomTitle(item)}
|
name={this.getRoomTitle(item)}
|
||||||
_updatedAt={item.roomUpdatedAt}
|
_updatedAt={item.roomUpdatedAt}
|
||||||
key={item._id}
|
key={item._id}
|
||||||
|
@ -480,15 +608,13 @@ class RoomsListView extends React.Component {
|
||||||
hideChannel={this.hideChannel}
|
hideChannel={this.hideChannel}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSectionHeader = header => (
|
renderSectionHeader = header => (
|
||||||
<View style={styles.groupTitleContainer}>
|
<View style={styles.groupTitleContainer}>
|
||||||
<Text style={styles.groupTitle}>{I18n.t(header)}</Text>
|
<Text style={styles.groupTitle}>{I18n.t(header)}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
);
|
||||||
|
|
||||||
renderSection = (data, header) => {
|
renderSection = (data, header) => {
|
||||||
const { showUnread, showFavorites, groupByType } = this.props;
|
const { showUnread, showFavorites, groupByType } = this.props;
|
||||||
|
@ -497,7 +623,15 @@ class RoomsListView extends React.Component {
|
||||||
return null;
|
return null;
|
||||||
} else if (header === 'Favorites' && !showFavorites) {
|
} else if (header === 'Favorites' && !showFavorites) {
|
||||||
return null;
|
return null;
|
||||||
} else if (['Discussions', 'Channels', 'Direct_Messages', 'Private_Groups', 'Livechat'].includes(header) && !groupByType) {
|
} else if (
|
||||||
|
[
|
||||||
|
'Discussions',
|
||||||
|
'Channels',
|
||||||
|
'Direct_Messages',
|
||||||
|
'Private_Groups'
|
||||||
|
].includes(header)
|
||||||
|
&& !groupByType
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
} else if (header === 'Chats' && groupByType) {
|
} else if (header === 'Chats' && groupByType) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -506,13 +640,14 @@ class RoomsListView extends React.Component {
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={data}
|
data={data}
|
||||||
|
extraData={data}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
ListHeaderComponent={() => this.renderSectionHeader(header)}
|
ListHeaderComponent={() => this.renderSectionHeader(header)}
|
||||||
getItemLayout={getItemLayout}
|
getItemLayout={getItemLayout}
|
||||||
enableEmptySections
|
enableEmptySections
|
||||||
removeClippedSubviews
|
removeClippedSubviews={isIOS}
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
initialNumToRender={12}
|
initialNumToRender={12}
|
||||||
windowSize={7}
|
windowSize={7}
|
||||||
|
@ -520,11 +655,18 @@ class RoomsListView extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
};
|
||||||
|
|
||||||
renderList = () => {
|
renderList = () => {
|
||||||
const {
|
const {
|
||||||
search, chats, unread, favorites, discussions, channels, direct, privateGroup, livechat
|
search,
|
||||||
|
chats,
|
||||||
|
unread,
|
||||||
|
favorites,
|
||||||
|
discussions,
|
||||||
|
channels,
|
||||||
|
direct,
|
||||||
|
privateGroup
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
if (search.length > 0) {
|
if (search.length > 0) {
|
||||||
|
@ -537,7 +679,7 @@ class RoomsListView extends React.Component {
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
getItemLayout={getItemLayout}
|
getItemLayout={getItemLayout}
|
||||||
enableEmptySections
|
enableEmptySections
|
||||||
removeClippedSubviews
|
removeClippedSubviews={isIOS}
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
initialNumToRender={12}
|
initialNumToRender={12}
|
||||||
windowSize={7}
|
windowSize={7}
|
||||||
|
@ -553,11 +695,10 @@ class RoomsListView extends React.Component {
|
||||||
{this.renderSection(channels, 'Channels')}
|
{this.renderSection(channels, 'Channels')}
|
||||||
{this.renderSection(direct, 'Direct_Messages')}
|
{this.renderSection(direct, 'Direct_Messages')}
|
||||||
{this.renderSection(privateGroup, 'Private_Groups')}
|
{this.renderSection(privateGroup, 'Private_Groups')}
|
||||||
{this.renderSection(livechat, 'Livechat')}
|
|
||||||
{this.renderSection(chats, 'Chats')}
|
{this.renderSection(chats, 'Chats')}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
renderScroll = () => {
|
renderScroll = () => {
|
||||||
const { loading } = this.state;
|
const { loading } = this.state;
|
||||||
|
@ -573,13 +714,14 @@ class RoomsListView extends React.Component {
|
||||||
<FlatList
|
<FlatList
|
||||||
ref={this.getScrollRef}
|
ref={this.getScrollRef}
|
||||||
data={search.length ? search : chats}
|
data={search.length ? search : chats}
|
||||||
|
extraData={search.length ? search : chats}
|
||||||
contentOffset={isIOS ? { x: 0, y: SCROLL_OFFSET } : {}}
|
contentOffset={isIOS ? { x: 0, y: SCROLL_OFFSET } : {}}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
ListHeaderComponent={this.renderListHeader}
|
ListHeaderComponent={this.renderListHeader}
|
||||||
getItemLayout={getItemLayout}
|
getItemLayout={getItemLayout}
|
||||||
// removeClippedSubviews
|
removeClippedSubviews={isIOS}
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
initialNumToRender={9}
|
initialNumToRender={9}
|
||||||
windowSize={9}
|
windowSize={9}
|
||||||
|
@ -598,20 +740,28 @@ class RoomsListView extends React.Component {
|
||||||
{this.renderList()}
|
{this.renderList()}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
console.count(`${ this.constructor.name }.render calls`);
|
console.count(`${ this.constructor.name }.render calls`);
|
||||||
const {
|
const {
|
||||||
sortBy, groupByType, showFavorites, showUnread, showServerDropdown, showSortDropdown
|
sortBy,
|
||||||
|
groupByType,
|
||||||
|
showFavorites,
|
||||||
|
showUnread,
|
||||||
|
showServerDropdown,
|
||||||
|
showSortDropdown
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container} testID='rooms-list-view' forceInset={{ vertical: 'never' }}>
|
<SafeAreaView
|
||||||
|
style={styles.container}
|
||||||
|
testID='rooms-list-view'
|
||||||
|
forceInset={{ vertical: 'never' }}
|
||||||
|
>
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
{this.renderScroll()}
|
{this.renderScroll()}
|
||||||
{showSortDropdown
|
{showSortDropdown ? (
|
||||||
? (
|
|
||||||
<SortDropdown
|
<SortDropdown
|
||||||
close={this.toggleSort}
|
close={this.toggleSort}
|
||||||
sortBy={sortBy}
|
sortBy={sortBy}
|
||||||
|
@ -619,13 +769,11 @@ class RoomsListView extends React.Component {
|
||||||
showFavorites={showFavorites}
|
showFavorites={showFavorites}
|
||||||
showUnread={showUnread}
|
showUnread={showUnread}
|
||||||
/>
|
/>
|
||||||
)
|
) : null}
|
||||||
: null
|
|
||||||
}
|
|
||||||
{showServerDropdown ? <ServerDropdown /> : null}
|
{showServerDropdown ? <ServerDropdown /> : null}
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
|
|
@ -25,7 +25,8 @@ class SearchMessagesView extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object,
|
navigation: PropTypes.object,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
baseUrl: PropTypes.string
|
baseUrl: PropTypes.string,
|
||||||
|
customEmojis: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -74,6 +75,15 @@ class SearchMessagesView extends React.Component {
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
|
getCustomEmoji = (name) => {
|
||||||
|
const { customEmojis } = this.props;
|
||||||
|
const emoji = customEmojis[name];
|
||||||
|
if (emoji) {
|
||||||
|
return emoji;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
renderEmpty = () => (
|
renderEmpty = () => (
|
||||||
<View style={styles.listEmptyContainer}>
|
<View style={styles.listEmptyContainer}>
|
||||||
<Text style={styles.noDataFound}>{I18n.t('No_results_found')}</Text>
|
<Text style={styles.noDataFound}>{I18n.t('No_results_found')}</Text>
|
||||||
|
@ -94,6 +104,7 @@ class SearchMessagesView extends React.Component {
|
||||||
isEdited={!!item.editedAt}
|
isEdited={!!item.editedAt}
|
||||||
isHeader
|
isHeader
|
||||||
onOpenFileModal={() => {}}
|
onOpenFileModal={() => {}}
|
||||||
|
getCustomEmoji={this.getCustomEmoji}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -145,7 +156,8 @@ const mapStateToProps = state => ({
|
||||||
id: state.login.user && state.login.user.id,
|
id: state.login.user && state.login.user.id,
|
||||||
username: state.login.user && state.login.user.username,
|
username: state.login.user && state.login.user.username,
|
||||||
token: state.login.user && state.login.user.token
|
token: state.login.user && state.login.user.token
|
||||||
}
|
},
|
||||||
|
customEmojis: state.customEmojis
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(SearchMessagesView);
|
export default connect(mapStateToProps)(SearchMessagesView);
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { connect } from 'react-redux';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
|
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import database from '../lib/realm';
|
|
||||||
import StatusBar from '../containers/StatusBar';
|
import StatusBar from '../containers/StatusBar';
|
||||||
import { COLOR_BACKGROUND_CONTAINER } from '../constants/colors';
|
import { COLOR_BACKGROUND_CONTAINER } from '../constants/colors';
|
||||||
import Navigation from '../lib/ShareNavigation';
|
import Navigation from '../lib/ShareNavigation';
|
||||||
|
@ -39,13 +38,14 @@ class SelectServerView extends React.Component {
|
||||||
})
|
})
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
server: PropTypes.string
|
server: PropTypes.string,
|
||||||
|
navigation: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const { serversDB } = database.databases;
|
const { navigation } = this.props;
|
||||||
const servers = serversDB.objects('servers');
|
const servers = navigation.getParam('servers', []);
|
||||||
const filteredServers = servers.filter(server => server.roomsUpdatedAt);
|
const filteredServers = servers.filter(server => server.roomsUpdatedAt);
|
||||||
this.state = {
|
this.state = {
|
||||||
servers: filteredServers
|
servers: filteredServers
|
||||||
|
|
|
@ -6,11 +6,13 @@ import {
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
import equal from 'deep-equal';
|
import equal from 'deep-equal';
|
||||||
|
import { orderBy } from 'lodash';
|
||||||
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addUser as addUserAction, removeUser as removeUserAction, reset as resetAction, setLoading as setLoadingAction
|
addUser as addUserAction, removeUser as removeUserAction, reset as resetAction, setLoading as setLoadingAction
|
||||||
} from '../actions/selectedUsers';
|
} from '../actions/selectedUsers';
|
||||||
import database, { safeAddListener } from '../lib/realm';
|
import database from '../lib/database';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import UserItem from '../presentation/UserItem';
|
import UserItem from '../presentation/UserItem';
|
||||||
import Loading from '../containers/Loading';
|
import Loading from '../containers/Loading';
|
||||||
|
@ -68,11 +70,11 @@ class SelectedUsersView extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.data = database.objects('subscriptions').filtered('t = $0', 'd').sorted('roomUpdatedAt', true);
|
this.init();
|
||||||
this.state = {
|
this.state = {
|
||||||
search: []
|
search: [],
|
||||||
|
chats: []
|
||||||
};
|
};
|
||||||
safeAddListener(this.data, this.updateState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -81,7 +83,7 @@ class SelectedUsersView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const { search } = this.state;
|
const { search, chats } = this.state;
|
||||||
const { users, loading } = this.props;
|
const { users, loading } = this.props;
|
||||||
if (nextProps.loading !== loading) {
|
if (nextProps.loading !== loading) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -92,14 +94,36 @@ class SelectedUsersView extends React.Component {
|
||||||
if (!equal(nextState.search, search)) {
|
if (!equal(nextState.search, search)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!equal(nextState.chats, chats)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
const { reset } = this.props;
|
const { reset } = this.props;
|
||||||
this.updateState.stop();
|
|
||||||
this.data.removeAllListeners();
|
|
||||||
reset();
|
reset();
|
||||||
|
if (this.querySubscription && this.querySubscription.unsubscribe) {
|
||||||
|
this.querySubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/sort-comp
|
||||||
|
init = async() => {
|
||||||
|
try {
|
||||||
|
const db = database.active;
|
||||||
|
const observable = await db.collections
|
||||||
|
.get('subscriptions')
|
||||||
|
.query(Q.where('t', 'd'))
|
||||||
|
.observeWithColumns(['room_updated_at']);
|
||||||
|
|
||||||
|
this.querySubscription = observable.subscribe((data) => {
|
||||||
|
const chats = orderBy(data, ['roomUpdatedAt'], ['desc']);
|
||||||
|
this.setState({ chats });
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchChangeText(text) {
|
onSearchChangeText(text) {
|
||||||
|
@ -208,7 +232,7 @@ class SelectedUsersView extends React.Component {
|
||||||
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />
|
renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />
|
||||||
|
|
||||||
renderItem = ({ item, index }) => {
|
renderItem = ({ item, index }) => {
|
||||||
const { search } = this.state;
|
const { search, chats } = this.state;
|
||||||
const { baseUrl, user } = this.props;
|
const { baseUrl, user } = this.props;
|
||||||
|
|
||||||
const name = item.search ? item.name : item.fname;
|
const name = item.search ? item.name : item.fname;
|
||||||
|
@ -220,7 +244,7 @@ class SelectedUsersView extends React.Component {
|
||||||
if (search.length > 0 && index === search.length - 1) {
|
if (search.length > 0 && index === search.length - 1) {
|
||||||
style = { ...style, ...sharedStyles.separatorBottom };
|
style = { ...style, ...sharedStyles.separatorBottom };
|
||||||
}
|
}
|
||||||
if (search.length === 0 && index === this.data.length - 1) {
|
if (search.length === 0 && index === chats.length - 1) {
|
||||||
style = { ...style, ...sharedStyles.separatorBottom };
|
style = { ...style, ...sharedStyles.separatorBottom };
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -238,10 +262,10 @@ class SelectedUsersView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList = () => {
|
renderList = () => {
|
||||||
const { search } = this.state;
|
const { search, chats } = this.state;
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={search.length > 0 ? search : this.data}
|
data={search.length > 0 ? search : chats}
|
||||||
extraData={this.props}
|
extraData={this.props}
|
||||||
keyExtractor={item => item._id}
|
keyExtractor={item => item._id}
|
||||||
renderItem={this.renderItem}
|
renderItem={this.renderItem}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue