Merge branch 'develop' into chore.console-error

This commit is contained in:
Reinaldo Neto 2022-02-04 15:13:05 -03:00 committed by GitHub
commit 6168926600
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
193 changed files with 3248 additions and 84920 deletions

View File

@ -359,7 +359,7 @@ jobs:
- run: - run:
name: Test name: Test
command: | command: |
yarn test yarn test -w 8
- run: - run:
name: Codecov name: Codecov

View File

@ -1,4 +1,5 @@
import initStoryshots from '@storybook/addon-storyshots'; import initStoryshots, { Stories2SnapsConverter } from '@storybook/addon-storyshots';
import { render } from '@testing-library/react-native';
jest.mock('rn-fetch-blob', () => ({ jest.mock('rn-fetch-blob', () => ({
fs: { fs: {
@ -19,4 +20,15 @@ jest.mock('react-native-file-viewer', () => ({
jest.mock('../app/lib/database', () => jest.fn(() => null)); jest.mock('../app/lib/database', () => jest.fn(() => null));
global.Date.now = jest.fn(() => new Date('2019-10-10').getTime()); global.Date.now = jest.fn(() => new Date('2019-10-10').getTime());
initStoryshots(); const converter = new Stories2SnapsConverter();
initStoryshots({
test: ({ story, context }) => {
const snapshotFilename = converter.getSnapshotFileName(context);
const storyElement = story.render();
const { update, toJSON } = render(storyElement);
update(storyElement);
const json = toJSON();
expect(JSON.stringify(json)).toMatchSpecificSnapshot(snapshotFilename);
}
});

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ import { connect } from 'react-redux';
import { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes'; import { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes';
import Navigation from './lib/Navigation'; import Navigation from './lib/Navigation';
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation'; import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME } from './actions/app'; import { RootEnum } from './definitions';
// Stacks // Stacks
import AuthLoadingView from './views/AuthLoadingView'; import AuthLoadingView from './views/AuthLoadingView';
// SetUsername Stack // SetUsername Stack
@ -56,13 +56,13 @@ const App = React.memo(({ root, isMasterDetail }: { root: string; isMasterDetail
}}> }}>
<Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}> <Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}>
<> <>
{root === ROOT_LOADING ? <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> : null} {root === RootEnum.ROOT_LOADING ? <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> : null}
{root === ROOT_OUTSIDE ? <Stack.Screen name='OutsideStack' component={OutsideStack} /> : null} {root === RootEnum.ROOT_OUTSIDE ? <Stack.Screen name='OutsideStack' component={OutsideStack} /> : null}
{root === ROOT_INSIDE && isMasterDetail ? ( {root === RootEnum.ROOT_INSIDE && isMasterDetail ? (
<Stack.Screen name='MasterDetailStack' component={MasterDetailStack} /> <Stack.Screen name='MasterDetailStack' component={MasterDetailStack} />
) : null} ) : null}
{root === ROOT_INSIDE && !isMasterDetail ? <Stack.Screen name='InsideStack' component={InsideStack} /> : null} {root === RootEnum.ROOT_INSIDE && !isMasterDetail ? <Stack.Screen name='InsideStack' component={InsideStack} /> : null}
{root === ROOT_SET_USERNAME ? <Stack.Screen name='SetUsernameStack' component={SetUsernameStack} /> : null} {root === RootEnum.ROOT_SET_USERNAME ? <Stack.Screen name='SetUsernameStack' component={SetUsernameStack} /> : null}
</> </>
</Stack.Navigator> </Stack.Navigator>
</NavigationContainer> </NavigationContainer>

View File

@ -2,8 +2,8 @@ const REQUEST = 'REQUEST';
const SUCCESS = 'SUCCESS'; const SUCCESS = 'SUCCESS';
const FAILURE = 'FAILURE'; const FAILURE = 'FAILURE';
const defaultTypes = [REQUEST, SUCCESS, FAILURE]; const defaultTypes = [REQUEST, SUCCESS, FAILURE];
function createRequestTypes(base = {}, types = defaultTypes): Record<any, any> { function createRequestTypes(base = {}, types = defaultTypes): Record<string, string> {
const res: Record<any, any> = {}; const res: Record<string, string> = {};
types.forEach(type => (res[type] = `${base}_${type}`)); types.forEach(type => (res[type] = `${base}_${type}`));
return res; return res;
} }

View File

@ -3,7 +3,7 @@ import { Action } from 'redux';
import { IActiveUsers } from '../reducers/activeUsers'; import { IActiveUsers } from '../reducers/activeUsers';
import { SET_ACTIVE_USERS } from './actionsTypes'; import { SET_ACTIVE_USERS } from './actionsTypes';
export interface ISetActiveUsers extends Action { interface ISetActiveUsers extends Action {
activeUsers: IActiveUsers; activeUsers: IActiveUsers;
} }

View File

@ -1,39 +0,0 @@
import { APP } from './actionsTypes';
export const ROOT_OUTSIDE = 'outside';
export const ROOT_INSIDE = 'inside';
export const ROOT_LOADING = 'loading';
export const ROOT_SET_USERNAME = 'setUsername';
export function appStart({ root, ...args }) {
return {
type: APP.START,
root,
...args
};
}
export function appReady() {
return {
type: APP.READY
};
}
export function appInit() {
return {
type: APP.INIT
};
}
export function appInitLocalSettings() {
return {
type: APP.INIT_LOCAL_SETTINGS
};
}
export function setMasterDetail(isMasterDetail) {
return {
type: APP.SET_MASTER_DETAIL,
isMasterDetail
};
}

53
app/actions/app.ts Normal file
View File

@ -0,0 +1,53 @@
import { Action } from 'redux';
import { RootEnum } from '../definitions';
import { APP } from './actionsTypes';
interface IAppStart extends Action {
root: RootEnum;
text?: string;
}
interface ISetMasterDetail extends Action {
isMasterDetail: boolean;
}
export type TActionApp = IAppStart & ISetMasterDetail;
interface Params {
root: RootEnum;
[key: string]: any;
}
export function appStart({ root, ...args }: Params): IAppStart {
return {
type: APP.START,
root,
...args
};
}
export function appReady(): Action {
return {
type: APP.READY
};
}
export function appInit(): Action {
return {
type: APP.INIT
};
}
export function appInitLocalSettings(): Action {
return {
type: APP.INIT_LOCAL_SETTINGS
};
}
export function setMasterDetail(isMasterDetail: boolean): ISetMasterDetail {
return {
type: APP.SET_MASTER_DETAIL,
isMasterDetail
};
}

View File

@ -1,20 +0,0 @@
import * as types from './actionsTypes';
export function connectRequest() {
return {
type: types.METEOR.REQUEST
};
}
export function connectSuccess() {
return {
type: types.METEOR.SUCCESS
};
}
export function disconnect(err) {
return {
type: types.METEOR.DISCONNECT,
err
};
}

21
app/actions/connect.ts Normal file
View File

@ -0,0 +1,21 @@
import { Action } from 'redux';
import * as types from './actionsTypes';
export function connectRequest(): Action {
return {
type: types.METEOR.REQUEST
};
}
export function connectSuccess(): Action {
return {
type: types.METEOR.SUCCESS
};
}
export function disconnect(): Action {
return {
type: types.METEOR.DISCONNECT
};
}

View File

@ -1,23 +0,0 @@
import * as types from './actionsTypes';
export function createChannelRequest(data) {
return {
type: types.CREATE_CHANNEL.REQUEST,
data
};
}
export function createChannelSuccess(data) {
return {
type: types.CREATE_CHANNEL.SUCCESS,
data
};
}
export function createChannelFailure(err, isTeam) {
return {
type: types.CREATE_CHANNEL.FAILURE,
err,
isTeam
};
}

View File

@ -0,0 +1,41 @@
import { Action } from 'redux';
import { TCreateChannelResult } from '../reducers/createChannel';
import { CREATE_CHANNEL } from './actionsTypes';
interface ICreateChannelRequest extends Action {
data: TCreateChannelResult;
}
interface ICreateChannelSuccess extends Action {
data: TCreateChannelResult;
}
interface ICreateChannelFailure extends Action {
err: any;
isTeam: boolean;
}
export type TActionCreateChannel = ICreateChannelRequest & ICreateChannelSuccess & ICreateChannelFailure;
export function createChannelRequest(data: TCreateChannelResult): ICreateChannelRequest {
return {
type: CREATE_CHANNEL.REQUEST,
data
};
}
export function createChannelSuccess(data: TCreateChannelResult): ICreateChannelSuccess {
return {
type: CREATE_CHANNEL.SUCCESS,
data
};
}
export function createChannelFailure(err: any, isTeam: boolean): ICreateChannelFailure {
return {
type: CREATE_CHANNEL.FAILURE,
err,
isTeam
};
}

View File

@ -1,22 +0,0 @@
import * as types from './actionsTypes';
export function createDiscussionRequest(data) {
return {
type: types.CREATE_DISCUSSION.REQUEST,
data
};
}
export function createDiscussionSuccess(data) {
return {
type: types.CREATE_DISCUSSION.SUCCESS,
data
};
}
export function createDiscussionFailure(err) {
return {
type: types.CREATE_DISCUSSION.FAILURE,
err
};
}

View File

@ -0,0 +1,38 @@
import { Action } from 'redux';
import { CREATE_DISCUSSION } from './actionsTypes';
interface ICreateDiscussionRequest extends Action {
data: any;
}
interface ICreateDiscussionSuccess extends Action {
data: any;
}
interface ICreateDiscussionFailure extends Action {
err: any;
}
export type TActionCreateDiscussion = ICreateDiscussionRequest & ICreateDiscussionSuccess & ICreateDiscussionFailure;
export function createDiscussionRequest(data: any): ICreateDiscussionRequest {
return {
type: CREATE_DISCUSSION.REQUEST,
data
};
}
export function createDiscussionSuccess(data: any): ICreateDiscussionSuccess {
return {
type: CREATE_DISCUSSION.SUCCESS,
data
};
}
export function createDiscussionFailure(err: any): ICreateDiscussionFailure {
return {
type: CREATE_DISCUSSION.FAILURE,
err
};
}

View File

@ -1,8 +0,0 @@
import * as types from './actionsTypes';
export function setCustomEmojis(emojis) {
return {
type: types.SET_CUSTOM_EMOJIS,
emojis
};
}

View File

@ -0,0 +1,17 @@
import { Action } from 'redux';
import { ICustomEmojis } from '../reducers/customEmojis';
import { SET_CUSTOM_EMOJIS } from './actionsTypes';
export interface ISetCustomEmojis extends Action {
emojis: ICustomEmojis;
}
export type TActionCustomEmojis = ISetCustomEmojis;
export function setCustomEmojis(emojis: ICustomEmojis): ISetCustomEmojis {
return {
type: SET_CUSTOM_EMOJIS,
emojis
};
}

View File

@ -1,8 +0,0 @@
import * as types from './actionsTypes';
export function deepLinkingOpen(params) {
return {
type: types.DEEP_LINKING.OPEN,
params
};
}

View File

@ -0,0 +1,25 @@
import { Action } from 'redux';
import { DEEP_LINKING } from './actionsTypes';
interface IParams {
path: string;
rid: string;
messageId: string;
host: string;
isCall: boolean;
fullURL: string;
type: string;
token: string;
}
interface IDeepLinkingOpen extends Action {
params: Partial<IParams>;
}
export function deepLinkingOpen(params: Partial<IParams>): IDeepLinkingOpen {
return {
type: DEEP_LINKING.OPEN,
params
};
}

View File

@ -1,35 +0,0 @@
import * as types from './actionsTypes';
export function encryptionInit() {
return {
type: types.ENCRYPTION.INIT
};
}
export function encryptionStop() {
return {
type: types.ENCRYPTION.STOP
};
}
export function encryptionSet(enabled = false, banner = null) {
return {
type: types.ENCRYPTION.SET,
enabled,
banner
};
}
export function encryptionSetBanner(banner) {
return {
type: types.ENCRYPTION.SET_BANNER,
banner
};
}
export function encryptionDecodeKey(password) {
return {
type: types.ENCRYPTION.DECODE_KEY,
password
};
}

52
app/actions/encryption.ts Normal file
View File

@ -0,0 +1,52 @@
import { Action } from 'redux';
import { IBanner } from '../reducers/encryption';
import { ENCRYPTION } from './actionsTypes';
export interface IEncryptionSet extends Action {
enabled: boolean;
banner: IBanner;
}
export interface IEncryptionSetBanner extends Action {
banner: IBanner;
}
export interface IEncryptionDecodeKey extends Action {
password: string;
}
export type TActionEncryption = IEncryptionSet & IEncryptionSetBanner & IEncryptionDecodeKey;
export function encryptionInit(): Action {
return {
type: ENCRYPTION.INIT
};
}
export function encryptionStop(): Action {
return {
type: ENCRYPTION.STOP
};
}
export function encryptionSet(enabled = false, banner: IBanner = ''): IEncryptionSet {
return {
type: ENCRYPTION.SET,
enabled,
banner
};
}
export function encryptionSetBanner(banner: IBanner = ''): IEncryptionSetBanner {
return {
type: ENCRYPTION.SET_BANNER,
banner
};
}
export function encryptionDecodeKey(password: string): IEncryptionDecodeKey {
return {
type: ENCRYPTION.DECODE_KEY,
password
};
}

View File

@ -1,54 +0,0 @@
import * as types from './actionsTypes';
export function inviteLinksSetToken(token) {
return {
type: types.INVITE_LINKS.SET_TOKEN,
token
};
}
export function inviteLinksRequest(token) {
return {
type: types.INVITE_LINKS.REQUEST,
token
};
}
export function inviteLinksSuccess() {
return {
type: types.INVITE_LINKS.SUCCESS
};
}
export function inviteLinksFailure() {
return {
type: types.INVITE_LINKS.FAILURE
};
}
export function inviteLinksClear() {
return {
type: types.INVITE_LINKS.CLEAR
};
}
export function inviteLinksCreate(rid) {
return {
type: types.INVITE_LINKS.CREATE,
rid
};
}
export function inviteLinksSetParams(params) {
return {
type: types.INVITE_LINKS.SET_PARAMS,
params
};
}
export function inviteLinksSetInvite(invite) {
return {
type: types.INVITE_LINKS.SET_INVITE,
invite
};
}

View File

@ -0,0 +1,61 @@
import { Action } from 'redux';
import { TInvite } from '../reducers/inviteLinks';
import { INVITE_LINKS } from './actionsTypes';
interface IInviteLinksGeneric extends Action {
token: string;
}
interface IInviteLinksCreate extends Action {
rid: string;
}
interface IInviteLinksSetInvite extends Action {
invite: TInvite;
}
type TParams = Record<string, any>;
interface IInviteLinksSetParams extends Action {
params: TParams;
}
export type TActionInviteLinks = IInviteLinksGeneric & IInviteLinksCreate & IInviteLinksSetInvite & IInviteLinksSetParams;
export const inviteLinksSetToken = (token: string): IInviteLinksGeneric => ({
type: INVITE_LINKS.SET_TOKEN,
token
});
export const inviteLinksRequest = (token: string): IInviteLinksGeneric => ({
type: INVITE_LINKS.REQUEST,
token
});
export const inviteLinksSuccess = (): Action => ({
type: INVITE_LINKS.SUCCESS
});
export const inviteLinksFailure = (): Action => ({
type: INVITE_LINKS.FAILURE
});
export const inviteLinksClear = (): Action => ({
type: INVITE_LINKS.CLEAR
});
export const inviteLinksCreate = (rid: string): IInviteLinksCreate => ({
type: INVITE_LINKS.CREATE,
rid
});
export const inviteLinksSetParams = (params: TParams): IInviteLinksSetParams => ({
type: INVITE_LINKS.SET_PARAMS,
params
});
export const inviteLinksSetInvite = (invite: TInvite): IInviteLinksSetInvite => ({
type: INVITE_LINKS.SET_INVITE,
invite
});

View File

@ -1,8 +0,0 @@
import * as types from './actionsTypes';
export function replyBroadcast(message) {
return {
type: types.MESSAGES.REPLY_BROADCAST,
message
};
}

16
app/actions/messages.ts Normal file
View File

@ -0,0 +1,16 @@
import { Action } from 'redux';
import { MESSAGES } from './actionsTypes';
type IMessage = Record<string, string>;
interface IReplyBroadcast extends Action {
message: IMessage;
}
export function replyBroadcast(message: IMessage): IReplyBroadcast {
return {
type: MESSAGES.REPLY_BROADCAST,
message
};
}

View File

@ -1,15 +0,0 @@
import * as types from './actionsTypes';
export function setPermissions(permissions) {
return {
type: types.PERMISSIONS.SET,
permissions
};
}
export function updatePermission(id, roles) {
return {
type: types.PERMISSIONS.UPDATE,
payload: { id, roles }
};
}

View File

@ -0,0 +1,28 @@
import { Action } from 'redux';
import { IPermissions } from '../reducers/permissions';
import { PERMISSIONS } from './actionsTypes';
interface ISetPermissions extends Action {
permissions: IPermissions;
}
interface IUpdatePermissions extends Action {
payload: { id: string; roles: string };
}
export type TActionPermissions = ISetPermissions & IUpdatePermissions;
export function setPermissions(permissions: IPermissions): ISetPermissions {
return {
type: PERMISSIONS.SET,
permissions
};
}
export function updatePermission(id: string, roles: string): IUpdatePermissions {
return {
type: PERMISSIONS.UPDATE,
payload: { id, roles }
};
}

View File

@ -1,20 +0,0 @@
import * as types from './actionsTypes';
export function setRoles(roles) {
return {
type: types.ROLES.SET,
roles
};
}
export function updateRoles(id, desc) {
return {
type: types.ROLES.UPDATE,
payload: { id, desc }
};
}
export function removeRoles(id) {
return {
type: types.ROLES.REMOVE,
payload: { id }
};
}

39
app/actions/roles.ts Normal file
View File

@ -0,0 +1,39 @@
import { Action } from 'redux';
import { IRoles } from '../reducers/roles';
import { ROLES } from './actionsTypes';
export interface ISetRoles extends Action {
roles: IRoles;
}
export interface IUpdateRoles extends Action {
payload: { id: string; desc: string };
}
export interface IRemoveRoles extends Action {
payload: { id: string };
}
export type IActionRoles = ISetRoles & IUpdateRoles & IRemoveRoles;
export function setRoles(roles: IRoles): ISetRoles {
return {
type: ROLES.SET,
roles
};
}
export function updateRoles(id: string, desc: string): IUpdateRoles {
return {
type: ROLES.UPDATE,
payload: { id, desc }
};
}
export function removeRoles(id: string): IRemoveRoles {
return {
type: ROLES.REMOVE,
payload: { id }
};
}

View File

@ -1,58 +0,0 @@
import * as types from './actionsTypes';
export function roomsRequest(params = { allData: false }) {
return {
type: types.ROOMS.REQUEST,
params
};
}
export function roomsSuccess() {
return {
type: types.ROOMS.SUCCESS
};
}
export function roomsFailure(err) {
return {
type: types.ROOMS.FAILURE,
err
};
}
export function roomsRefresh() {
return {
type: types.ROOMS.REFRESH
};
}
export function setSearch(searchText) {
return {
type: types.ROOMS.SET_SEARCH,
searchText
};
}
export function closeServerDropdown() {
return {
type: types.ROOMS.CLOSE_SERVER_DROPDOWN
};
}
export function toggleServerDropdown() {
return {
type: types.ROOMS.TOGGLE_SERVER_DROPDOWN
};
}
export function openSearchHeader() {
return {
type: types.ROOMS.OPEN_SEARCH_HEADER
};
}
export function closeSearchHeader() {
return {
type: types.ROOMS.CLOSE_SEARCH_HEADER
};
}

78
app/actions/rooms.ts Normal file
View File

@ -0,0 +1,78 @@
import { Action } from 'redux';
import { ROOMS } from './actionsTypes';
export interface IRoomsRequest extends Action {
params: any;
}
export interface ISetSearch extends Action {
searchText: string;
}
export interface IRoomsFailure extends Action {
err: Record<string, any> | string;
}
export type IRoomsAction = IRoomsRequest & ISetSearch & IRoomsFailure;
export function roomsRequest(
params: {
allData: boolean;
} = { allData: false }
): IRoomsRequest {
return {
type: ROOMS.REQUEST,
params
};
}
export function roomsSuccess(): Action {
return {
type: ROOMS.SUCCESS
};
}
export function roomsFailure(err: string): IRoomsFailure {
return {
type: ROOMS.FAILURE,
err
};
}
export function roomsRefresh(): Action {
return {
type: ROOMS.REFRESH
};
}
export function setSearch(searchText: string): ISetSearch {
return {
type: ROOMS.SET_SEARCH,
searchText
};
}
export function closeServerDropdown(): Action {
return {
type: ROOMS.CLOSE_SERVER_DROPDOWN
};
}
export function toggleServerDropdown(): Action {
return {
type: ROOMS.TOGGLE_SERVER_DROPDOWN
};
}
export function openSearchHeader(): Action {
return {
type: ROOMS.OPEN_SEARCH_HEADER
};
}
export function closeSearchHeader(): Action {
return {
type: ROOMS.CLOSE_SEARCH_HEADER
};
}

View File

@ -1,60 +0,0 @@
import { SERVER } from './actionsTypes';
export function selectServerRequest(server, version, fetchVersion = true, changeServer = false) {
return {
type: SERVER.SELECT_REQUEST,
server,
version,
fetchVersion,
changeServer
};
}
export function selectServerSuccess(server, version) {
return {
type: SERVER.SELECT_SUCCESS,
server,
version
};
}
export function selectServerFailure() {
return {
type: SERVER.SELECT_FAILURE
};
}
export function serverRequest(server, username = null, fromServerHistory = false) {
return {
type: SERVER.REQUEST,
server,
username,
fromServerHistory
};
}
export function serverSuccess() {
return {
type: SERVER.SUCCESS
};
}
export function serverFailure(err) {
return {
type: SERVER.FAILURE,
err
};
}
export function serverInitAdd(previousServer) {
return {
type: SERVER.INIT_ADD,
previousServer
};
}
export function serverFinishAdd() {
return {
type: SERVER.FINISH_ADD
};
}

90
app/actions/server.ts Normal file
View File

@ -0,0 +1,90 @@
import { Action } from 'redux';
import { SERVER } from './actionsTypes';
interface ISelectServer extends Action {
server: string;
version?: string;
fetchVersion: boolean;
changeServer: boolean;
}
interface ISelectServerSuccess extends Action {
server: string;
version: string;
}
interface IServer extends Action {
server: string;
username: string | null;
fromServerHistory: boolean;
}
interface IServerInit extends Action {
previousServer: string;
}
interface IServerFailure extends Action {
err: any;
}
export type TActionServer = ISelectServer & ISelectServerSuccess & IServer & IServerInit & IServerFailure;
export function selectServerRequest(server: string, version?: string, fetchVersion = true, changeServer = false): ISelectServer {
return {
type: SERVER.SELECT_REQUEST,
server,
version,
fetchVersion,
changeServer
};
}
export function selectServerSuccess(server: string, version: string): ISelectServerSuccess {
return {
type: SERVER.SELECT_SUCCESS,
server,
version
};
}
export function selectServerFailure(): Action {
return {
type: SERVER.SELECT_FAILURE
};
}
export function serverRequest(server: string, username: string | null = null, fromServerHistory = false): IServer {
return {
type: SERVER.REQUEST,
server,
username,
fromServerHistory
};
}
export function serverSuccess(): Action {
return {
type: SERVER.SUCCESS
};
}
export function serverFailure(err: any): IServerFailure {
return {
type: SERVER.FAILURE,
err
};
}
export function serverInitAdd(previousServer: string): IServerInit {
return {
type: SERVER.INIT_ADD,
previousServer
};
}
export function serverFinishAdd(): Action {
return {
type: SERVER.FINISH_ADD
};
}

View File

@ -1,21 +0,0 @@
import { SETTINGS } from './actionsTypes';
export function addSettings(settings) {
return {
type: SETTINGS.ADD,
payload: settings
};
}
export function updateSettings(id, value) {
return {
type: SETTINGS.UPDATE,
payload: { id, value }
};
}
export function clearSettings() {
return {
type: SETTINGS.CLEAR
};
}

34
app/actions/settings.ts Normal file
View File

@ -0,0 +1,34 @@
import { Action } from 'redux';
import { ISettings, TSettings } from '../reducers/settings';
import { SETTINGS } from './actionsTypes';
interface IAddSettings extends Action {
payload: ISettings;
}
interface IUpdateSettings extends Action {
payload: { id: string; value: TSettings };
}
export type IActionSettings = IAddSettings & IUpdateSettings;
export function addSettings(settings: ISettings): IAddSettings {
return {
type: SETTINGS.ADD,
payload: settings
};
}
export function updateSettings(id: string, value: TSettings): IUpdateSettings {
return {
type: SETTINGS.UPDATE,
payload: { id, value }
};
}
export function clearSettings(): Action {
return {
type: SETTINGS.CLEAR
};
}

View File

@ -1,22 +0,0 @@
import { SHARE } from './actionsTypes';
export function shareSelectServer(server) {
return {
type: SHARE.SELECT_SERVER,
server
};
}
export function shareSetSettings(settings) {
return {
type: SHARE.SET_SETTINGS,
settings
};
}
export function shareSetUser(user) {
return {
type: SHARE.SET_USER,
user
};
}

39
app/actions/share.ts Normal file
View File

@ -0,0 +1,39 @@
import { Action } from 'redux';
import { IShareServer, IShareUser, TShareSettings } from '../reducers/share';
import { SHARE } from './actionsTypes';
interface IShareSelectServer extends Action {
server: IShareServer;
}
interface IShareSetSettings extends Action {
settings: TShareSettings;
}
interface IShareSetUser extends Action {
user: IShareUser;
}
export type TActionsShare = IShareSelectServer & IShareSetSettings & IShareSetUser;
export function shareSelectServer(server: IShareServer): IShareSelectServer {
return {
type: SHARE.SELECT_SERVER,
server
};
}
export function shareSetSettings(settings: TShareSettings): IShareSetSettings {
return {
type: SHARE.SET_SETTINGS,
settings
};
}
export function shareSetUser(user: IShareUser): IShareSetUser {
return {
type: SHARE.SET_USER,
user
};
}

View File

@ -1,15 +0,0 @@
import * as types from './actionsTypes';
export function setAllPreferences(preferences) {
return {
type: types.SORT_PREFERENCES.SET_ALL,
preferences
};
}
export function setPreference(preference) {
return {
type: types.SORT_PREFERENCES.SET,
preference
};
}

View File

@ -0,0 +1,28 @@
import { Action } from 'redux';
import { IPreferences } from '../definitions';
import { SORT_PREFERENCES } from './actionsTypes';
interface ISetAllPreferences extends Action {
preferences: IPreferences;
}
interface ISetPreference extends Action {
preference: Partial<IPreferences>;
}
export type TActionSortPreferences = ISetAllPreferences & ISetPreference;
export function setAllPreferences(preferences: IPreferences): ISetAllPreferences {
return {
type: SORT_PREFERENCES.SET_ALL,
preferences
};
}
export function setPreference(preference: Partial<IPreferences>): ISetPreference {
return {
type: SORT_PREFERENCES.SET,
preference
};
}

View File

@ -1,21 +0,0 @@
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
};
}

View File

@ -0,0 +1,29 @@
import { Action } from 'redux';
import { USERS_TYPING } from './actionsTypes';
export interface IUsersTypingGenericAction extends Action {
username: string;
}
export type TActionUserTyping = IUsersTypingGenericAction & Action;
export function addUserTyping(username: string): IUsersTypingGenericAction {
return {
type: USERS_TYPING.ADD,
username
};
}
export function removeUserTyping(username: string): IUsersTypingGenericAction {
return {
type: USERS_TYPING.REMOVE,
username
};
}
export function clearUserTyping(): Action {
return {
type: USERS_TYPING.CLEAR
};
}

View File

@ -1,16 +1,18 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { Observable, Subscription } from 'rxjs';
import database from '../../lib/database'; import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import { TSubscriptionModel, TUserModel } from '../../definitions';
import Avatar from './Avatar'; import Avatar from './Avatar';
import { IAvatar } from './interfaces'; import { IAvatar } from './interfaces';
class AvatarContainer extends React.Component<IAvatar, any> { class AvatarContainer extends React.Component<IAvatar, any> {
private mounted: boolean; private mounted: boolean;
private subscription: any; private subscription?: Subscription;
static defaultProps = { static defaultProps = {
text: '', text: '',
@ -59,15 +61,17 @@ class AvatarContainer extends React.Component<IAvatar, any> {
record = user; record = user;
} else { } else {
const { rid } = this.props; const { rid } = this.props;
record = await subsCollection.find(rid); if (rid) {
record = await subsCollection.find(rid);
}
} }
} catch { } catch {
// Record not found // Record not found
} }
if (record) { if (record) {
const observable = record.observe(); const observable = record.observe() as Observable<TSubscriptionModel | TUserModel>;
this.subscription = observable.subscribe((r: any) => { this.subscription = observable.subscribe(r => {
const { avatarETag } = r; const { avatarETag } = r;
if (this.mounted) { if (this.mounted) {
this.setState({ avatarETag }); this.setState({ avatarETag });

View File

@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots BackgroundContainer basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]}]}"`;
exports[`Storyshots BackgroundContainer black theme - loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_black\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"animating\\":true,\\"color\\":\\"#f9f9f9\\",\\"hidesWhenStopped\\":true,\\"size\\":\\"small\\",\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"}},\\"children\\":null}]}"`;
exports[`Storyshots BackgroundContainer black theme - text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_black\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#f9f9f9\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
exports[`Storyshots BackgroundContainer dark theme - loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_dark\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"animating\\":true,\\"color\\":\\"#f9f9f9\\",\\"hidesWhenStopped\\":true,\\"size\\":\\"small\\",\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"}},\\"children\\":null}]}"`;
exports[`Storyshots BackgroundContainer dark theme - text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_dark\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#f9f9f9\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
exports[`Storyshots BackgroundContainer loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"animating\\":true,\\"color\\":\\"#6C727A\\",\\"hidesWhenStopped\\":true,\\"size\\":\\"small\\",\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"}},\\"children\\":null}]}"`;
exports[`Storyshots BackgroundContainer long text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
exports[`Storyshots BackgroundContainer text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Text here\\"]}]}"`;

View File

@ -35,7 +35,7 @@ const styles = StyleSheet.create({
const BackgroundContainer = ({ theme, text, loading }: IBackgroundContainer) => ( const BackgroundContainer = ({ theme, text, loading }: IBackgroundContainer) => (
<View style={styles.container}> <View style={styles.container}>
<ImageBackground source={{ uri: `message_empty_${theme}` }} style={styles.image} /> <ImageBackground source={{ uri: `message_empty_${theme}` }} style={styles.image} />
{text ? <Text style={[styles.text, { color: themes[theme!].auxiliaryTintColor }]}>{text}</Text> : null} {text && !loading ? <Text style={[styles.text, { color: themes[theme!].auxiliaryTintColor }]}>{text}</Text> : null}
{loading ? <ActivityIndicator style={styles.text} color={themes[theme!].auxiliaryTintColor} /> : null} {loading ? <ActivityIndicator style={styles.text} color={themes[theme!].auxiliaryTintColor} /> : null}
</View> </View>
); );

View File

@ -36,7 +36,7 @@ interface IEmojiPickerProps {
} }
interface IEmojiPickerState { interface IEmojiPickerState {
frequentlyUsed: []; frequentlyUsed: (string | { content?: string; extension?: string; isCustom: boolean })[];
customEmojis: any; customEmojis: any;
show: boolean; show: boolean;
width: number | null; width: number | null;
@ -114,7 +114,7 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
// Do nothing // Do nothing
} }
await db.action(async () => { await db.write(async () => {
if (freqEmojiRecord) { if (freqEmojiRecord) {
await freqEmojiRecord.update((f: any) => { await freqEmojiRecord.update((f: any) => {
f.count += 1; f.count += 1;
@ -132,8 +132,8 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
updateFrequentlyUsed = async () => { updateFrequentlyUsed = async () => {
const db = database.active; const db = database.active;
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch(); const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
let frequentlyUsed: any = orderBy(frequentlyUsedRecords, ['count'], ['desc']); const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
frequentlyUsed = frequentlyUsed.map((item: IEmoji) => { const frequentlyUsed = frequentlyUsedOrdered.map(item => {
if (item.isCustom) { if (item.isCustom) {
return { content: item.content, extension: item.extension, isCustom: item.isCustom }; return { content: item.content, extension: item.extension, isCustom: item.isCustom };
} }

View File

@ -10,6 +10,7 @@ import database from '../../lib/database';
import { Button } from '../ActionSheet'; import { Button } from '../ActionSheet';
import { useDimensions } from '../../dimensions'; import { useDimensions } from '../../dimensions';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import { TFrequentlyUsedEmojiModel } from '../../definitions/IFrequentlyUsedEmoji';
import { IEmoji } from '../EmojiPicker/interfaces'; import { IEmoji } from '../EmojiPicker/interfaces';
interface IHeader { interface IHeader {
@ -90,14 +91,14 @@ const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => (
)); ));
const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => { const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => {
const [items, setItems] = useState([]); const [items, setItems] = useState<(TFrequentlyUsedEmojiModel | string)[]>([]);
const { width, height }: any = useDimensions(); const { width, height }: any = useDimensions();
const setEmojis = async () => { const setEmojis = async () => {
try { try {
const db = database.active; const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis'); const freqEmojiCollection = db.get('frequently_used_emojis');
let freqEmojis = await freqEmojiCollection.query().fetch(); let freqEmojis: (TFrequentlyUsedEmojiModel | string)[] = await freqEmojiCollection.query().fetch();
const isLandscape = width > height; const isLandscape = width > height;
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2; const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;

View File

@ -15,6 +15,7 @@ import { showConfirmationAlert } from '../../utils/info';
import { useActionSheet } from '../ActionSheet'; import { useActionSheet } from '../ActionSheet';
import Header, { HEADER_HEIGHT } from './Header'; import Header, { HEADER_HEIGHT } from './Header';
import events from '../../utils/log/events'; import events from '../../utils/log/events';
import { TMessageModel } from '../../definitions/IMessage';
interface IMessageActions { interface IMessageActions {
room: { room: {
@ -182,9 +183,9 @@ const MessageActions = React.memo(
if (result.success) { if (result.success) {
const subCollection = db.get('subscriptions'); const subCollection = db.get('subscriptions');
const subRecord = await subCollection.find(rid); const subRecord = await subCollection.find(rid);
await db.action(async () => { await db.write(async () => {
try { try {
await subRecord.update((sub: any) => (sub.lastOpen = ts)); await subRecord.update(sub => (sub.lastOpen = ts));
} catch { } catch {
// do nothing // do nothing
} }
@ -269,11 +270,11 @@ const MessageActions = React.memo(
} }
}; };
const handleToggleTranslation = async (message: any) => { const handleToggleTranslation = async (message: TMessageModel) => {
try { try {
const db = database.active; const db = database.active;
await db.action(async () => { await db.write(async () => {
await message.update((m: any) => { await message.update(m => {
m.autoTranslate = !m.autoTranslate; m.autoTranslate = !m.autoTranslate;
m._updatedAt = new Date(); m._updatedAt = new Date();
}); });
@ -320,7 +321,7 @@ const MessageActions = React.memo(
}); });
}; };
const getOptions = (message: any) => { const getOptions = (message: TMessageModel) => {
let options: any = []; let options: any = [];
// Reply // Reply
@ -446,7 +447,7 @@ const MessageActions = React.memo(
return options; return options;
}; };
const showMessageActions = async (message: any) => { const showMessageActions = async (message: TMessageModel) => {
logEvent(events.ROOM_SHOW_MSG_ACTIONS); logEvent(events.ROOM_SHOW_MSG_ACTIONS);
await getPermissions(); await getPermissions();
showActionSheet({ showActionSheet({

View File

@ -36,7 +36,7 @@ const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
try { try {
// Find the thread header and update it // Find the thread header and update it
const msg = await msgCollection.find(tmid); const msg = await msgCollection.find(tmid);
if (msg.tcount <= 1) { if (msg?.tcount && msg.tcount <= 1) {
deleteBatch.push( deleteBatch.push(
msg.prepareUpdate((m: any) => { msg.prepareUpdate((m: any) => {
m.tcount = null; m.tcount = null;
@ -62,7 +62,7 @@ const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
// Do nothing: message not found // Do nothing: message not found
} }
} }
await db.action(async () => { await db.write(async () => {
await db.batch(...deleteBatch); await db.batch(...deleteBatch);
}); });
} catch (e) { } catch (e) {

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,11 @@
import { dequal } from 'dequal';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { dequal } from 'dequal';
import RoomHeader from './RoomHeader'; import { IApplicationState } from '../../definitions';
import { withDimensions } from '../../dimensions'; import { withDimensions } from '../../dimensions';
import I18n from '../../i18n'; import I18n from '../../i18n';
import RoomHeader from './RoomHeader';
interface IRoomHeaderContainerProps { interface IRoomHeaderContainerProps {
title: string; title: string;
@ -122,8 +123,8 @@ class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> {
} }
} }
const mapStateToProps = (state: any, ownProps: any) => { const mapStateToProps = (state: IApplicationState, ownProps: any) => {
let statusText; let statusText = '';
let status = 'offline'; let status = 'offline';
const { roomUserId, type, visitor = {}, tmid } = ownProps; const { roomUserId, type, visitor = {}, tmid } = ownProps;

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import { StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
import { withTheme } from '../theme'; import I18n from '../i18n';
import { useTheme } from '../theme';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import TextInput from '../presentation/TextInput'; import TextInput from '../presentation/TextInput';
@ -19,14 +20,13 @@ const styles = StyleSheet.create({
} }
}); });
interface ISearchHeader { interface ISearchHeaderProps {
theme?: string;
onSearchChangeText?: (text: string) => void; onSearchChangeText?: (text: string) => void;
testID: string;
} }
// TODO: it might be useful to refactor this component for reusage const SearchHeader = ({ onSearchChangeText, testID }: ISearchHeaderProps): JSX.Element => {
const SearchHeader = ({ theme, onSearchChangeText }: ISearchHeader) => { const { theme } = useTheme();
const titleColorStyle = { color: themes[theme!].headerTitleColor };
const isLight = theme === 'light'; const isLight = theme === 'light';
const { isLandscape } = useOrientation(); const { isLandscape } = useOrientation();
const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1; const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1;
@ -36,14 +36,14 @@ const SearchHeader = ({ theme, onSearchChangeText }: ISearchHeader) => {
<View style={styles.container}> <View style={styles.container}>
<TextInput <TextInput
autoFocus autoFocus
style={[styles.title, isLight && titleColorStyle, { fontSize: titleFontSize }]} style={[styles.title, isLight && { color: themes[theme].headerTitleColor }, { fontSize: titleFontSize }]}
placeholder='Search' placeholder={I18n.t('Search')}
onChangeText={onSearchChangeText} onChangeText={onSearchChangeText}
theme={theme!} theme={theme}
testID='thread-messages-view-search-header' testID={testID}
/> />
</View> </View>
); );
}; };
export default withTheme(SearchHeader); export default SearchHeader;

View File

@ -5,7 +5,7 @@ import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
import { withTheme } from '../theme'; import { useTheme } from '../theme';
import { TThreadModel } from '../definitions/IThread'; import { TThreadModel } from '../definitions/IThread';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -48,12 +48,12 @@ interface IThreadDetails {
badgeColor?: string; badgeColor?: string;
toggleFollowThread: Function; toggleFollowThread: Function;
style: ViewStyle; style: ViewStyle;
theme?: string;
} }
const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, theme }: IThreadDetails) => { const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style }: IThreadDetails): JSX.Element => {
const { theme } = useTheme();
let { tcount } = item; let { tcount } = item;
if (tcount! >= 1000) { if (tcount && tcount >= 1000) {
tcount = '+999'; tcount = '+999';
} }
@ -81,7 +81,6 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, them
</Text> </Text>
</View> </View>
</View> </View>
<View style={styles.badgeContainer}> <View style={styles.badgeContainer}>
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null} {badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null}
<Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}> <Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}>
@ -96,4 +95,4 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, them
); );
}; };
export default withTheme(ThreadDetails); export default ThreadDetails;

View File

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Text Input Short and Long Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"paddingHorizontal\\":14}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},null]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10,\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"color\\":\\"#0d0e12\\"},null]},\\"children\\":[\\"Short Text\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"allowFontScaling\\":true,\\"rejectResponderTermination\\":true,\\"underlineColorAndroid\\":\\"transparent\\",\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,null,{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"accessibilityLabel\\":\\"placeholder\\",\\"placeholder\\":\\"placeholder\\",\\"value\\":\\"Rocket.Chat\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},null]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10,\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"color\\":\\"#0d0e12\\"},null]},\\"children\\":[\\"Long Text\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"allowFontScaling\\":true,\\"rejectResponderTermination\\":true,\\"underlineColorAndroid\\":\\"transparent\\",\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,null,{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"accessibilityLabel\\":\\"placeholder\\",\\"placeholder\\":\\"placeholder\\",\\"value\\":\\"https://open.rocket.chat/images/logo/android-chrome-512x512.png\\"},\\"children\\":null}]}]}]}"`;

View File

@ -25,7 +25,7 @@ import { isValidURL } from '../../utils/url';
import NewMarkdown from './new'; import NewMarkdown from './new';
interface IMarkdownProps { interface IMarkdownProps {
msg: string; msg?: string;
md: MarkdownAST; md: MarkdownAST;
mentions: UserMention[]; mentions: UserMention[];
getCustomEmoji: Function; getCustomEmoji: Function;

View File

@ -182,7 +182,7 @@ const Fields = React.memo(
<Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text> <Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text>
{/* @ts-ignore*/} {/* @ts-ignore*/}
<Markdown <Markdown
msg={field.value!} msg={field?.value || ''}
baseUrl={baseUrl} baseUrl={baseUrl}
username={user.username} username={user.username}
getCustomEmoji={getCustomEmoji} getCustomEmoji={getCustomEmoji}

View File

@ -24,7 +24,6 @@ const Thread = React.memo(
item={{ item={{
tcount, tcount,
replies, replies,
tlm,
id id
}} }}
user={user} user={user}

View File

@ -147,6 +147,12 @@ class MessageContainer extends React.Component<IMessageContainerProps> {
if ((item.tlm || item.tmid) && !isThreadRoom) { if ((item.tlm || item.tmid) && !isThreadRoom) {
this.onThreadPress(); this.onThreadPress();
} }
const { onDiscussionPress } = this.props;
if (onDiscussionPress) {
onDiscussionPress(item);
}
}, },
300, 300,
true true

View File

@ -1,5 +1,7 @@
import { MarkdownAST } from '@rocket.chat/message-parser'; import { MarkdownAST } from '@rocket.chat/message-parser';
export type TMessageType = 'discussion-created' | 'jitsi_call_started';
export interface IMessageAttachments { export interface IMessageAttachments {
attachments: any; attachments: any;
timeFormat: string; timeFormat: string;
@ -101,7 +103,7 @@ export interface IMessageThread {
msg: string; msg: string;
tcount: number; tcount: number;
theme: string; theme: string;
tlm: string; tlm: Date;
isThreadRoom: boolean; isThreadRoom: boolean;
id: string; id: string;
} }
@ -140,7 +142,7 @@ export interface IMessageInner
IMessageThread, IMessageThread,
IMessageAttachments, IMessageAttachments,
IMessageBroadcast { IMessageBroadcast {
type: string; type: TMessageType;
blocks: []; blocks: [];
} }

View File

@ -1,3 +1,4 @@
import { TMessageModel } from '../../definitions/IMessage';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { DISCUSSION } from './constants'; import { DISCUSSION } from './constants';
@ -149,7 +150,7 @@ export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage) => {
return ''; return '';
}; };
export const getMessageTranslation = (message: { translations: any }, autoTranslateLanguage: string) => { export const getMessageTranslation = (message: TMessageModel, autoTranslateLanguage: string) => {
if (!autoTranslateLanguage) { if (!autoTranslateLanguage) {
return null; return null;
} }

View File

@ -7,4 +7,4 @@ export interface IFrequentlyUsedEmoji {
count: number; count: number;
} }
export type TFrequentlyUsedEmoji = IFrequentlyUsedEmoji & Model; export type TFrequentlyUsedEmojiModel = IFrequentlyUsedEmoji & Model;

View File

@ -15,4 +15,4 @@ export interface ILoggedUser {
enableMessageParserEarlyAdoption?: boolean; enableMessageParserEarlyAdoption?: boolean;
} }
export type TLoggedUser = ILoggedUser & Model; export type TLoggedUserModel = ILoggedUser & Model;

View File

@ -0,0 +1,10 @@
import { SortBy, DisplayMode } from '../constants/constantDisplayMode';
export interface IPreferences {
sortBy: SortBy;
groupByType: boolean;
showFavorites: boolean;
showUnread: boolean;
showAvatar: boolean;
displayMode: DisplayMode;
}

View File

@ -7,4 +7,4 @@ export interface IServerHistory {
updatedAt: Date; updatedAt: Date;
} }
export type TServerHistory = IServerHistory & Model; export type TServerHistoryModel = IServerHistory & Model;

View File

@ -79,8 +79,6 @@ export interface ISubscription {
avatarETag?: string; avatarETag?: string;
teamId?: string; teamId?: string;
teamMain?: boolean; teamMain?: boolean;
search?: boolean;
username?: string;
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api // https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
messages: Relation<TMessageModel>; messages: Relation<TMessageModel>;
threads: Relation<TThreadModel>; threads: Relation<TThreadModel>;

View File

@ -43,7 +43,7 @@ export interface IThread {
id: string; id: string;
msg?: string; msg?: string;
t?: SubscriptionType; t?: SubscriptionType;
rid?: string; rid: string;
_updatedAt?: Date; _updatedAt?: Date;
ts?: Date; ts?: Date;
u?: IUserMessage; u?: IUserMessage;
@ -61,10 +61,10 @@ export interface IThread {
reactions?: IReaction[]; reactions?: IReaction[];
role?: string; role?: string;
drid?: string; drid?: string;
dcount?: number; dcount?: number | string;
dlm?: number; dlm?: number;
tmid?: string; tmid?: string;
tcount: number | string; tcount?: number | string;
tlm?: string; tlm?: string;
replies?: string[]; replies?: string[];
mentions?: IUserMention[]; mentions?: IUserMention[];

View File

@ -3,11 +3,24 @@ import { StackNavigationProp } from '@react-navigation/stack';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
export * from './IAttachment'; export * from './IAttachment';
export * from './IMessage';
export * from './INotification'; export * from './INotification';
export * from './IRoom'; export * from './IPreferences';
export * from './IServer';
export * from './ISubscription'; export * from './ISubscription';
export * from './IRoom';
export * from './IMessage';
export * from './IThread';
export * from './IThreadMessage';
export * from './ICustomEmoji';
export * from './IFrequentlyUsedEmoji';
export * from './IUpload';
export * from './ISettings';
export * from './IRole';
export * from './IPermission';
export * from './ISlashCommand';
export * from './IUser';
export * from './IServer';
export * from './ILoggedUser';
export * from './IServerHistory';
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> { export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {
navigation: StackNavigationProp<T, S>; navigation: StackNavigationProp<T, S>;
@ -17,3 +30,4 @@ export interface IBaseScreen<T extends Record<string, object | undefined>, S ext
} }
export * from './redux'; export * from './redux';
export * from './redux/TRootEnum';

View File

@ -0,0 +1,6 @@
export enum RootEnum {
ROOT_OUTSIDE = 'outside',
ROOT_INSIDE = 'inside',
ROOT_LOADING = 'loading',
ROOT_SET_USERNAME = 'setUsername'
}

View File

@ -1,31 +1,67 @@
import { TActionSelectedUsers } from '../../actions/selectedUsers'; // ACTIONS
import { TActionActiveUsers } from '../../actions/activeUsers'; import { TActionActiveUsers } from '../../actions/activeUsers';
import { TActionApp } from '../../actions/app';
import { TActionCreateChannel } from '../../actions/createChannel';
import { TActionCreateDiscussion } from '../../actions/createDiscussion';
import { TActionCustomEmojis } from '../../actions/customEmojis';
import { TActionEncryption } from '../../actions/encryption';
import { TActionInviteLinks } from '../../actions/inviteLinks';
import { IActionRoles } from '../../actions/roles';
import { TActionSelectedUsers } from '../../actions/selectedUsers';
import { TActionServer } from '../../actions/server';
import { IActionSettings } from '../../actions/settings';
import { TActionsShare } from '../../actions/share';
import { TActionSortPreferences } from '../../actions/sortPreferences';
import { TActionUserTyping } from '../../actions/usersTyping';
// REDUCERS // REDUCERS
import { IActiveUsers } from '../../reducers/activeUsers'; import { IActiveUsers } from '../../reducers/activeUsers';
import { IApp } from '../../reducers/app';
import { IConnect } from '../../reducers/connect';
import { ICreateChannel } from '../../reducers/createChannel';
import { ICreateDiscussion } from '../../reducers/createDiscussion';
import { IEncryption } from '../../reducers/encryption';
import { IInviteLinks } from '../../reducers/inviteLinks';
import { IRoles } from '../../reducers/roles';
import { ISelectedUsers } from '../../reducers/selectedUsers'; import { ISelectedUsers } from '../../reducers/selectedUsers';
import { IServer } from '../../reducers/server';
import { ISettings } from '../../reducers/settings';
import { IShare } from '../../reducers/share';
export interface IApplicationState { export interface IApplicationState {
settings: any; settings: ISettings;
login: any; login: any;
meteor: any; meteor: IConnect;
server: any; server: IServer;
selectedUsers: ISelectedUsers; selectedUsers: ISelectedUsers;
createChannel: any; app: IApp;
app: any; createChannel: ICreateChannel;
room: any; room: any;
rooms: any; rooms: any;
sortPreferences: any; sortPreferences: any;
share: any; share: IShare;
customEmojis: any; customEmojis: any;
activeUsers: IActiveUsers; activeUsers: IActiveUsers;
usersTyping: any; usersTyping: any;
inviteLinks: any; inviteLinks: IInviteLinks;
createDiscussion: any; createDiscussion: ICreateDiscussion;
inquiry: any; inquiry: any;
enterpriseModules: any; enterpriseModules: any;
encryption: any; encryption: IEncryption;
permissions: any; permissions: any;
roles: any; roles: IRoles;
} }
export type TApplicationActions = TActionActiveUsers & TActionSelectedUsers; export type TApplicationActions = TActionActiveUsers &
TActionSelectedUsers &
TActionCustomEmojis &
TActionInviteLinks &
IActionRoles &
IActionSettings &
TActionEncryption &
TActionSortPreferences &
TActionUserTyping &
TActionCreateDiscussion&
TActionCreateChannel &
TActionsShare &
TActionServer &
TActionApp;

View File

@ -775,6 +775,7 @@
"creating_discussion": "creating discussion", "creating_discussion": "creating discussion",
"Canned_Responses": "Canned Responses", "Canned_Responses": "Canned Responses",
"No_match_found": "No match found.", "No_match_found": "No match found.",
"No_discussions": "No discussions",
"Check_canned_responses": "Check on canned responses.", "Check_canned_responses": "Check on canned responses.",
"Searching": "Searching", "Searching": "Searching",
"Use": "Use", "Use": "Use",

View File

@ -25,6 +25,7 @@ import serversSchema from './schema/servers';
import appSchema from './schema/app'; import appSchema from './schema/app';
import migrations from './model/migrations'; import migrations from './model/migrations';
import serversMigrations from './model/servers/migrations'; import serversMigrations from './model/servers/migrations';
import { TAppDatabase, TServerDatabase } from './interfaces';
const appGroupPath = isIOS ? appGroup.path : ''; const appGroupPath = isIOS ? appGroup.path : '';
@ -32,9 +33,9 @@ if (__DEV__ && isIOS) {
console.log(appGroupPath); console.log(appGroupPath);
} }
const getDatabasePath = name => `${appGroupPath}${name}${isOfficial ? '' : '-experimental'}.db`; const getDatabasePath = (name: string) => `${appGroupPath}${name}${isOfficial ? '' : '-experimental'}.db`;
export const getDatabase = (database = '') => { export const getDatabase = (database = ''): Database => {
const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.'); const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.');
const dbName = getDatabasePath(path); const dbName = getDatabasePath(path);
@ -64,8 +65,14 @@ export const getDatabase = (database = '') => {
}); });
}; };
interface IDatabases {
shareDB?: TAppDatabase;
serversDB: TServerDatabase;
activeDB?: TAppDatabase;
}
class DB { class DB {
databases = { databases: IDatabases = {
serversDB: new Database({ serversDB: new Database({
adapter: new SQLiteAdapter({ adapter: new SQLiteAdapter({
dbName: getDatabasePath('default'), dbName: getDatabasePath('default'),
@ -73,11 +80,12 @@ class DB {
migrations: serversMigrations migrations: serversMigrations
}), }),
modelClasses: [Server, LoggedUser, ServersHistory] modelClasses: [Server, LoggedUser, ServersHistory]
}) }) as TServerDatabase
}; };
get active() { // Expected at least one database
return this.databases.shareDB || this.databases.activeDB; get active(): TAppDatabase {
return this.databases.shareDB || this.databases.activeDB!;
} }
get share() { get share() {
@ -116,11 +124,11 @@ class DB {
Setting, Setting,
User User
] ]
}); }) as TAppDatabase;
} }
setActiveDB(database) { setActiveDB(database: string) {
this.databases.activeDB = getDatabase(database); this.databases.activeDB = getDatabase(database) as TAppDatabase;
} }
} }

View File

@ -0,0 +1,72 @@
import { Database, Collection } from '@nozbe/watermelondb';
import * as models from './model';
import * as definitions from '../../definitions';
export type TAppDatabaseNames =
| typeof models.SUBSCRIPTIONS_TABLE
| typeof models.ROOMS_TABLE
| typeof models.MESSAGES_TABLE
| typeof models.THREADS_TABLE
| typeof models.THREAD_MESSAGES_TABLE
| typeof models.CUSTOM_EMOJIS_TABLE
| typeof models.FREQUENTLY_USED_EMOJIS_TABLE
| typeof models.UPLOADS_TABLE
| typeof models.SETTINGS_TABLE
| typeof models.ROLES_TABLE
| typeof models.PERMISSIONS_TABLE
| typeof models.SLASH_COMMANDS_TABLE
| typeof models.USERS_TABLE;
// Verify if T extends one type from TAppDatabaseNames, and if is truly,
// returns the specific model type.
// https://stackoverflow.com/a/54166010 TypeScript function return type based on input parameter
type ObjectType<T> = T extends typeof models.SUBSCRIPTIONS_TABLE
? definitions.TSubscriptionModel
: T extends typeof models.ROOMS_TABLE
? definitions.TRoomModel
: T extends typeof models.MESSAGES_TABLE
? definitions.TMessageModel
: T extends typeof models.THREADS_TABLE
? definitions.TThreadModel
: T extends typeof models.THREAD_MESSAGES_TABLE
? definitions.TThreadMessageModel
: T extends typeof models.CUSTOM_EMOJIS_TABLE
? definitions.TCustomEmojiModel
: T extends typeof models.FREQUENTLY_USED_EMOJIS_TABLE
? definitions.TFrequentlyUsedEmojiModel
: T extends typeof models.UPLOADS_TABLE
? definitions.TUploadModel
: T extends typeof models.SETTINGS_TABLE
? definitions.TSettingsModel
: T extends typeof models.ROLES_TABLE
? definitions.TRoleModel
: T extends typeof models.PERMISSIONS_TABLE
? definitions.TPermissionModel
: T extends typeof models.SLASH_COMMANDS_TABLE
? definitions.TSlashCommandModel
: T extends typeof models.USERS_TABLE
? definitions.TUserModel
: never;
export type TAppDatabase = {
get: <T extends TAppDatabaseNames>(db: T) => Collection<ObjectType<T>>;
} & Database;
// Migration to server database
export type TServerDatabaseNames =
| typeof models.SERVERS_TABLE
| typeof models.LOGGED_USERS_TABLE
| typeof models.SERVERS_HISTORY_TABLE;
type ObjectServerType<T> = T extends typeof models.SERVERS_TABLE
? definitions.TServerModel
: T extends typeof models.LOGGED_USERS_TABLE
? definitions.TLoggedUserModel
: T extends typeof models.SERVERS_HISTORY_TABLE
? definitions.TServerHistoryModel
: never;
export type TServerDatabase = {
get: <T extends TServerDatabaseNames>(db: T) => Collection<ObjectServerType<T>>;
} & Database;

View File

@ -3,8 +3,10 @@ import { date, field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const CUSTOM_EMOJIS_TABLE = 'custom_emojis';
export default class CustomEmoji extends Model { export default class CustomEmoji extends Model {
static table = 'custom_emojis'; static table = CUSTOM_EMOJIS_TABLE;
@field('name') name; @field('name') name;

View File

@ -1,8 +1,9 @@
import { Model } from '@nozbe/watermelondb'; import { Model } from '@nozbe/watermelondb';
import { field } from '@nozbe/watermelondb/decorators'; import { field } from '@nozbe/watermelondb/decorators';
export const FREQUENTLY_USED_EMOJIS_TABLE = 'frequently_used_emojis';
export default class FrequentlyUsedEmoji extends Model { export default class FrequentlyUsedEmoji extends Model {
static table = 'frequently_used_emojis'; static table = FREQUENTLY_USED_EMOJIS_TABLE;
@field('content') content; @field('content') content;

View File

@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const TABLE_NAME = 'messages'; export const MESSAGES_TABLE = 'messages';
export default class Message extends Model { export default class Message extends Model {
static table = TABLE_NAME; static table = MESSAGES_TABLE;
static associations = { static associations = {
subscriptions: { type: 'belongs_to', key: 'rid' } subscriptions: { type: 'belongs_to', key: 'rid' }

View File

@ -3,8 +3,10 @@ import { date, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const PERMISSIONS_TABLE = 'permissions';
export default class Permission extends Model { export default class Permission extends Model {
static table = 'permissions'; static table = PERMISSIONS_TABLE;
@json('roles', sanitizer) roles; @json('roles', sanitizer) roles;

View File

@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb'; import { Model } from '@nozbe/watermelondb';
import { field } from '@nozbe/watermelondb/decorators'; import { field } from '@nozbe/watermelondb/decorators';
export const ROLES_TABLE = 'roles';
export default class Role extends Model { export default class Role extends Model {
static table = 'roles'; static table = ROLES_TABLE;
@field('description') description; @field('description') description;
} }

View File

@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const ROOMS_TABLE = 'rooms';
export default class Room extends Model { export default class Room extends Model {
static table = 'rooms'; static table = ROOMS_TABLE;
@json('custom_fields', sanitizer) customFields; @json('custom_fields', sanitizer) customFields;

View File

@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb'; import { Model } from '@nozbe/watermelondb';
import { date, field, readonly } from '@nozbe/watermelondb/decorators'; import { date, field, readonly } from '@nozbe/watermelondb/decorators';
export const SERVERS_HISTORY_TABLE = 'servers_history';
export default class ServersHistory extends Model { export default class ServersHistory extends Model {
static table = 'servers_history'; static table = SERVERS_HISTORY_TABLE;
@field('url') url; @field('url') url;

View File

@ -3,8 +3,10 @@ import { date, field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const SETTINGS_TABLE = 'settings';
export default class Setting extends Model { export default class Setting extends Model {
static table = 'settings'; static table = SETTINGS_TABLE;
@field('value_as_string') valueAsString; @field('value_as_string') valueAsString;

View File

@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb'; import { Model } from '@nozbe/watermelondb';
import { field } from '@nozbe/watermelondb/decorators'; import { field } from '@nozbe/watermelondb/decorators';
export const SLASH_COMMANDS_TABLE = 'slash_commands';
export default class SlashCommand extends Model { export default class SlashCommand extends Model {
static table = 'slash_commands'; static table = SLASH_COMMANDS_TABLE;
@field('params') params; @field('params') params;

View File

@ -3,10 +3,10 @@ import { children, date, field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const TABLE_NAME = 'subscriptions'; export const SUBSCRIPTIONS_TABLE = 'subscriptions';
export default class Subscription extends Model { export default class Subscription extends Model {
static table = TABLE_NAME; static table = SUBSCRIPTIONS_TABLE;
static associations = { static associations = {
messages: { type: 'has_many', foreignKey: 'rid' }, messages: { type: 'has_many', foreignKey: 'rid' },

View File

@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const TABLE_NAME = 'threads'; export const THREADS_TABLE = 'threads';
export default class Thread extends Model { export default class Thread extends Model {
static table = TABLE_NAME; static table = THREADS_TABLE;
static associations = { static associations = {
subscriptions: { type: 'belongs_to', key: 'rid' } subscriptions: { type: 'belongs_to', key: 'rid' }

View File

@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const TABLE_NAME = 'thread_messages'; export const THREAD_MESSAGES_TABLE = 'thread_messages';
export default class ThreadMessage extends Model { export default class ThreadMessage extends Model {
static table = TABLE_NAME; static table = THREAD_MESSAGES_TABLE;
static associations = { static associations = {
subscriptions: { type: 'belongs_to', key: 'subscription_id' } subscriptions: { type: 'belongs_to', key: 'subscription_id' }

View File

@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb'; import { Model } from '@nozbe/watermelondb';
import { field, relation } from '@nozbe/watermelondb/decorators'; import { field, relation } from '@nozbe/watermelondb/decorators';
export const UPLOADS_TABLE = 'uploads';
export default class Upload extends Model { export default class Upload extends Model {
static table = 'uploads'; static table = UPLOADS_TABLE;
static associations = { static associations = {
subscriptions: { type: 'belongs_to', key: 'rid' } subscriptions: { type: 'belongs_to', key: 'rid' }

View File

@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const USERS_TABLE = 'users';
export default class User extends Model { export default class User extends Model {
static table = 'users'; static table = USERS_TABLE;
@field('_id') _id; @field('_id') _id;

View File

@ -0,0 +1,16 @@
export * from './CustomEmoji';
export * from './FrequentlyUsedEmoji';
export * from './Message';
export * from './Permission';
export * from './Role';
export * from './Room';
export * from './Setting';
export * from './SlashCommand';
export * from './Subscription';
export * from './Thread';
export * from './ThreadMessage';
export * from './Upload';
export * from './User';
export * from './ServersHistory';
export * from './servers/Server';
export * from './servers/User';

View File

@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb'; import { Model } from '@nozbe/watermelondb';
import { date, field } from '@nozbe/watermelondb/decorators'; import { date, field } from '@nozbe/watermelondb/decorators';
export const SERVERS_TABLE = 'servers';
export default class Server extends Model { export default class Server extends Model {
static table = 'servers'; static table = SERVERS_TABLE;
@field('name') name; @field('name') name;

View File

@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../../utils'; import { sanitizer } from '../../utils';
export const LOGGED_USERS_TABLE = 'users';
export default class User extends Model { export default class User extends Model {
static table = 'users'; static table = LOGGED_USERS_TABLE;
@field('token') token; @field('token') token;

View File

@ -1,7 +1,7 @@
import database from '..'; import database from '..';
import { TABLE_NAME } from '../model/Message'; import { MESSAGES_TABLE } from '../model/Message';
const getCollection = db => db.get(TABLE_NAME); const getCollection = db => db.get(MESSAGES_TABLE);
export const getMessageById = async messageId => { export const getMessageById = async messageId => {
const db = database.active; const db = database.active;

View File

@ -1,7 +1,7 @@
import database from '..'; import database from '..';
import { TABLE_NAME } from '../model/Subscription'; import { SUBSCRIPTIONS_TABLE } from '../model/Subscription';
const getCollection = db => db.get(TABLE_NAME); const getCollection = db => db.get(SUBSCRIPTIONS_TABLE);
export const getSubscriptionByRoomId = async rid => { export const getSubscriptionByRoomId = async rid => {
const db = database.active; const db = database.active;

View File

@ -1,7 +1,7 @@
import database from '..'; import database from '..';
import { TABLE_NAME } from '../model/Thread'; import { THREADS_TABLE } from '../model/Thread';
const getCollection = db => db.get(TABLE_NAME); const getCollection = db => db.get(THREADS_TABLE);
export const getThreadById = async tmid => { export const getThreadById = async tmid => {
const db = database.active; const db = database.active;

View File

@ -1,7 +1,7 @@
import database from '..'; import database from '..';
import { TABLE_NAME } from '../model/ThreadMessage'; import { THREAD_MESSAGES_TABLE } from '../model/ThreadMessage';
const getCollection = db => db.get(TABLE_NAME); const getCollection = db => db.get(THREAD_MESSAGES_TABLE);
export const getThreadMessageById = async messageId => { export const getThreadMessageById = async messageId => {
const db = database.active; const db = database.active;

View File

@ -61,7 +61,7 @@ const PERMISSIONS = [
export async function setPermissions() { export async function setPermissions() {
const db = database.active; const db = database.active;
const permissionsCollection = db.collections.get('permissions'); const permissionsCollection = db.get('permissions');
const allPermissions = await permissionsCollection.query(Q.where('id', Q.oneOf(PERMISSIONS))).fetch(); const allPermissions = await permissionsCollection.query(Q.where('id', Q.oneOf(PERMISSIONS))).fetch();
const parsed = allPermissions.reduce((acc, item) => ({ ...acc, [item.id]: item.roles }), {}); const parsed = allPermissions.reduce((acc, item) => ({ ...acc, [item.id]: item.roles }), {});

View File

@ -8,7 +8,7 @@ import protectedFunction from './helpers/protectedFunction';
export async function setRoles() { export async function setRoles() {
const db = database.active; const db = database.active;
const rolesCollection = db.collections.get('roles'); const rolesCollection = db.get('roles');
const allRoles = await rolesCollection.query().fetch(); const allRoles = await rolesCollection.query().fetch();
const parsed = allRoles.reduce((acc, item) => ({ ...acc, [item.id]: item.description || item.id }), {}); const parsed = allRoles.reduce((acc, item) => ({ ...acc, [item.id]: item.description || item.id }), {});
reduxStore.dispatch(setRolesAction(parsed)); reduxStore.dispatch(setRolesAction(parsed));

View File

@ -811,6 +811,16 @@ const RocketChat = {
encrypted encrypted
}); });
}, },
getDiscussions({ roomId, offset, count, text }) {
const params = {
roomId,
offset,
count,
...(text && { text })
};
// RC 2.4.0
return this.sdk.get('chat.getDiscussions', params);
},
createTeam({ name, users, type, readOnly, broadcast, encrypted }) { createTeam({ name, users, type, readOnly, broadcast, encrypted }) {
const params = { const params = {
name, name,

View File

@ -4,7 +4,7 @@ import { SET_ACTIVE_USERS } from '../actions/actionsTypes';
type TUserStatus = 'online' | 'offline'; type TUserStatus = 'online' | 'offline';
export interface IActiveUser { export interface IActiveUser {
status: TUserStatus; status: TUserStatus;
statusText?: string; statusText: string;
} }
export interface IActiveUsers { export interface IActiveUsers {

44
app/reducers/app.test.ts Normal file
View File

@ -0,0 +1,44 @@
import { appStart, appInit, setMasterDetail } from '../actions/app';
import { initialState } from './app';
import { mockedStore } from './mockedStore';
import { RootEnum } from '../definitions';
import { APP_STATE } from '../actions/actionsTypes';
describe('test reducer', () => {
it('should return initial state', () => {
const state = mockedStore.getState().app;
expect(state).toEqual(initialState);
});
it('should return root state after dispatch appStart action', () => {
mockedStore.dispatch(appStart({ root: RootEnum.ROOT_INSIDE }));
const { root } = mockedStore.getState().app;
expect(root).toEqual(RootEnum.ROOT_INSIDE);
});
it('should return ready state after dispatch appInit action', () => {
mockedStore.dispatch(appInit());
const { ready } = mockedStore.getState().app;
expect(ready).toEqual(false);
});
it('should return ready state after dispatch setMasterDetail action', () => {
mockedStore.dispatch(setMasterDetail(false));
const { isMasterDetail } = mockedStore.getState().app;
expect(isMasterDetail).toEqual(false);
});
it('should return correct state after app go to foreground', () => {
mockedStore.dispatch({ type: APP_STATE.FOREGROUND });
const { foreground, background } = mockedStore.getState().app;
expect(foreground).toEqual(true);
expect(background).toEqual(false);
});
it('should return correct state after app go to background', () => {
mockedStore.dispatch({ type: APP_STATE.BACKGROUND });
const { foreground, background } = mockedStore.getState().app;
expect(foreground).toEqual(false);
expect(background).toEqual(true);
});
});

View File

@ -1,15 +1,26 @@
import { TActionApp } from '../actions/app';
import { RootEnum } from '../definitions';
import { APP, APP_STATE } from '../actions/actionsTypes'; import { APP, APP_STATE } from '../actions/actionsTypes';
const initialState = { export interface IApp {
root: null, root?: RootEnum;
isMasterDetail: boolean;
text?: string;
ready: boolean;
foreground: boolean;
background: boolean;
}
export const initialState: IApp = {
root: undefined,
isMasterDetail: false, isMasterDetail: false,
text: null, text: undefined,
ready: false, ready: false,
foreground: true, foreground: true,
background: false background: false
}; };
export default function app(state = initialState, action) { export default function app(state = initialState, action: TActionApp): IApp {
switch (action.type) { switch (action.type) {
case APP_STATE.FOREGROUND: case APP_STATE.FOREGROUND:
return { return {

View File

@ -0,0 +1,28 @@
import { connectRequest, connectSuccess, disconnect } from '../actions/connect';
import { initialState } from './connect';
import { mockedStore } from './mockedStore';
describe('test reducer', () => {
it('should return initial state', () => {
const { meteor } = mockedStore.getState();
expect(meteor).toEqual(initialState);
});
it('should return correct meteor state after dispatch connectRequest action', () => {
mockedStore.dispatch(connectRequest());
const { meteor } = mockedStore.getState();
expect(meteor).toEqual({ connecting: true, connected: false });
});
it('should return correct meteor state after dispatch connectSuccess action', () => {
mockedStore.dispatch(connectSuccess());
const { meteor } = mockedStore.getState();
expect(meteor).toEqual({ connecting: false, connected: true });
});
it('should return correct meteor state after dispatch disconnect action', () => {
mockedStore.dispatch(disconnect());
const { meteor } = mockedStore.getState();
expect(meteor).toEqual(initialState);
});
});

View File

@ -1,11 +1,18 @@
import { Action } from 'redux';
import { METEOR } from '../actions/actionsTypes'; import { METEOR } from '../actions/actionsTypes';
const initialState = { export interface IConnect {
connecting: boolean;
connected: boolean;
}
export const initialState: IConnect = {
connecting: false, connecting: false,
connected: false connected: false
}; };
export default function connect(state = initialState, action) { export default function connect(state = initialState, action: Action): IConnect {
switch (action.type) { switch (action.type) {
case METEOR.REQUEST: case METEOR.REQUEST:
return { return {

View File

@ -1,36 +0,0 @@
import { CREATE_CHANNEL } from '../actions/actionsTypes';
const initialState = {
isFetching: false,
failure: false,
result: {},
error: {}
};
export default function (state = initialState, action) {
switch (action.type) {
case CREATE_CHANNEL.REQUEST:
return {
...state,
isFetching: true,
failure: false,
error: {}
};
case CREATE_CHANNEL.SUCCESS:
return {
...state,
isFetching: false,
failure: false,
result: action.data
};
case CREATE_CHANNEL.FAILURE:
return {
...state,
isFetching: false,
failure: true,
error: action.err
};
default:
return state;
}
}

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