parent
5fafe376fa
commit
2eb723a6dc
215 changed files with 2703 additions and 2735 deletions
2
Makefile
2
Makefile
|
|
@ -95,7 +95,7 @@ post-install:
|
|||
fi
|
||||
@sed -i'' -e 's|transform: \[{scaleY: -1}\],|...Platform.select({android: {transform: \[{perspective: 1}, {scaleY: -1}\]}, ios: {transform: \[{scaleY: -1}\]}}),|g' node_modules/react-native/Libraries/Lists/VirtualizedList.js
|
||||
|
||||
@./node_modules/.bin/patch-package --patch-dir=native_modules
|
||||
@./node_modules/.bin/patch-package
|
||||
|
||||
start: | pre-run ## Starts the React Native packager server
|
||||
$(call start_packager)
|
||||
|
|
|
|||
|
|
@ -1,394 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Platform} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import merge from 'deepmerge';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
|
||||
export function resetToChannel(passProps = {}) {
|
||||
return (dispatch, getState) => {
|
||||
const theme = getTheme(getState());
|
||||
|
||||
Navigation.setRoot({
|
||||
root: {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name: 'Channel',
|
||||
passProps,
|
||||
options: {
|
||||
layout: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
visible: false,
|
||||
height: 0,
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function resetToSelectServer(allowOtherServers) {
|
||||
return (dispatch, getState) => {
|
||||
const theme = getTheme(getState());
|
||||
|
||||
Navigation.setRoot({
|
||||
root: {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name: 'SelectServer',
|
||||
passProps: {
|
||||
allowOtherServers,
|
||||
},
|
||||
options: {
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
visible: false,
|
||||
height: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function resetToTeams(name, title, passProps = {}, options = {}) {
|
||||
return (dispatch, getState) => {
|
||||
const theme = getTheme(getState());
|
||||
const defaultOptions = {
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
visible: true,
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: title,
|
||||
},
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Navigation.setRoot({
|
||||
root: {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function goToScreen(name, title, passProps = {}, options = {}) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
const theme = getTheme(state);
|
||||
const defaultOptions = {
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
topBar: {
|
||||
animate: true,
|
||||
visible: true,
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: title,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Navigation.push(componentId, {
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function popTopScreen(screenId) {
|
||||
return () => {
|
||||
if (screenId) {
|
||||
Navigation.pop(screenId);
|
||||
} else {
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
Navigation.pop(componentId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function popToRoot() {
|
||||
return () => {
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
|
||||
Navigation.popToRoot(componentId).catch(() => {
|
||||
// RNN returns a promise rejection if there are no screens
|
||||
// atop the root screen to pop. We'll do nothing in this
|
||||
// case but we will catch the rejection here so that the
|
||||
// caller doesn't have to.
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function showModal(name, title, passProps = {}, options = {}) {
|
||||
return (dispatch, getState) => {
|
||||
const theme = getTheme(getState());
|
||||
const defaultOptions = {
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
animate: true,
|
||||
visible: true,
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: title,
|
||||
},
|
||||
leftButtonColor: theme.sidebarHeaderTextColor,
|
||||
rightButtonColor: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
};
|
||||
|
||||
Navigation.showModal({
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
}],
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function showModalOverCurrentContext(name, passProps = {}, options = {}) {
|
||||
return (dispatch) => {
|
||||
const title = '';
|
||||
const animationsEnabled = (Platform.OS === 'android').toString();
|
||||
const defaultOptions = {
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
layout: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
topBar: {
|
||||
visible: false,
|
||||
height: 0,
|
||||
},
|
||||
animations: {
|
||||
showModal: {
|
||||
enabled: animationsEnabled,
|
||||
alpha: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
duration: 250,
|
||||
},
|
||||
},
|
||||
dismissModal: {
|
||||
enabled: animationsEnabled,
|
||||
alpha: {
|
||||
from: 1,
|
||||
to: 0,
|
||||
duration: 250,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const mergeOptions = merge(defaultOptions, options);
|
||||
|
||||
dispatch(showModal(name, title, passProps, mergeOptions));
|
||||
};
|
||||
}
|
||||
|
||||
export function showSearchModal(initialValue = '') {
|
||||
return (dispatch) => {
|
||||
const name = 'Search';
|
||||
const title = '';
|
||||
const passProps = {initialValue};
|
||||
const options = {
|
||||
topBar: {
|
||||
visible: false,
|
||||
height: 0,
|
||||
},
|
||||
};
|
||||
|
||||
dispatch(showModal(name, title, passProps, options));
|
||||
};
|
||||
}
|
||||
|
||||
export function dismissModal(options = {}) {
|
||||
return () => {
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
|
||||
Navigation.dismissModal(componentId, options).catch(() => {
|
||||
// RNN returns a promise rejection if there is no modal to
|
||||
// dismiss. We'll do nothing in this case but we will catch
|
||||
// the rejection here so that the caller doesn't have to.
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function dismissAllModals(options = {}) {
|
||||
return () => {
|
||||
Navigation.dismissAllModals(options).catch(() => {
|
||||
// RNN returns a promise rejection if there are no modals to
|
||||
// dismiss. We'll do nothing in this case but we will catch
|
||||
// the rejection here so that the caller doesn't have to.
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function peek(name, passProps = {}, options = {}) {
|
||||
return () => {
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
const defaultOptions = {
|
||||
preview: {
|
||||
commit: false,
|
||||
},
|
||||
};
|
||||
|
||||
Navigation.push(componentId, {
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function setButtons(componentId, buttons = {leftButtons: [], rightButtons: []}) {
|
||||
return () => {
|
||||
Navigation.mergeOptions(componentId, {
|
||||
topBar: {
|
||||
...buttons,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function showOverlay(name, passProps, options = {}) {
|
||||
return () => {
|
||||
const defaultOptions = {
|
||||
overlay: {
|
||||
interceptTouchOutside: false,
|
||||
},
|
||||
};
|
||||
|
||||
Navigation.showOverlay({
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function dismissOverlay(componentId) {
|
||||
return () => {
|
||||
return Navigation.dismissOverlay(componentId).catch(() => {
|
||||
// RNN returns a promise rejection if there is no modal with
|
||||
// this componentId to dismiss. We'll do nothing in this case
|
||||
// but we will catch the rejection here so that the caller
|
||||
// doesn't have to.
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function applyTheme(componentId, skipBackButtonStyle = false) {
|
||||
return (dispatch, getState) => {
|
||||
const theme = getTheme(getState());
|
||||
|
||||
let backButton = {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
};
|
||||
|
||||
if (skipBackButtonStyle && Platform.OS === 'android') {
|
||||
backButton = null;
|
||||
}
|
||||
|
||||
Navigation.mergeOptions(componentId, {
|
||||
topBar: {
|
||||
backButton,
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
352
app/actions/navigation/index.js
Normal file
352
app/actions/navigation/index.js
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Platform} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import merge from 'deepmerge';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import store from 'app/store';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
|
||||
function getThemeFromState() {
|
||||
const state = store.getState();
|
||||
|
||||
return getTheme(state);
|
||||
}
|
||||
|
||||
export function resetToChannel(passProps = {}) {
|
||||
const theme = getThemeFromState();
|
||||
|
||||
Navigation.setRoot({
|
||||
root: {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name: 'Channel',
|
||||
passProps,
|
||||
options: {
|
||||
layout: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
visible: false,
|
||||
height: 0,
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function resetToSelectServer(allowOtherServers) {
|
||||
const theme = getThemeFromState();
|
||||
|
||||
Navigation.setRoot({
|
||||
root: {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name: 'SelectServer',
|
||||
passProps: {
|
||||
allowOtherServers,
|
||||
},
|
||||
options: {
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
visible: false,
|
||||
height: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function resetToTeams(name, title, passProps = {}, options = {}) {
|
||||
const theme = getThemeFromState();
|
||||
const defaultOptions = {
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
visible: true,
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: title,
|
||||
},
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Navigation.setRoot({
|
||||
root: {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function goToScreen(name, title, passProps = {}, options = {}) {
|
||||
const theme = getThemeFromState();
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
const defaultOptions = {
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
topBar: {
|
||||
animate: true,
|
||||
visible: true,
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: title,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Navigation.push(componentId, {
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function popTopScreen(screenId) {
|
||||
if (screenId) {
|
||||
Navigation.pop(screenId);
|
||||
} else {
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
Navigation.pop(componentId);
|
||||
}
|
||||
}
|
||||
|
||||
export async function popToRoot() {
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
|
||||
try {
|
||||
await Navigation.popToRoot(componentId);
|
||||
} catch (error) {
|
||||
// RNN returns a promise rejection if there are no screens
|
||||
// atop the root screen to pop. We'll do nothing in this case.
|
||||
}
|
||||
}
|
||||
|
||||
export function showModal(name, title, passProps = {}, options = {}) {
|
||||
const theme = getThemeFromState();
|
||||
const defaultOptions = {
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
animate: true,
|
||||
visible: true,
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: title,
|
||||
},
|
||||
leftButtonColor: theme.sidebarHeaderTextColor,
|
||||
rightButtonColor: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
};
|
||||
|
||||
Navigation.showModal({
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
}],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function showModalOverCurrentContext(name, passProps = {}, options = {}) {
|
||||
const title = '';
|
||||
const animationsEnabled = (Platform.OS === 'android').toString();
|
||||
const defaultOptions = {
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
layout: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
topBar: {
|
||||
visible: false,
|
||||
height: 0,
|
||||
},
|
||||
animations: {
|
||||
showModal: {
|
||||
enabled: animationsEnabled,
|
||||
alpha: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
duration: 250,
|
||||
},
|
||||
},
|
||||
dismissModal: {
|
||||
enabled: animationsEnabled,
|
||||
alpha: {
|
||||
from: 1,
|
||||
to: 0,
|
||||
duration: 250,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const mergeOptions = merge(defaultOptions, options);
|
||||
|
||||
showModal(name, title, passProps, mergeOptions);
|
||||
}
|
||||
|
||||
export function showSearchModal(initialValue = '') {
|
||||
const name = 'Search';
|
||||
const title = '';
|
||||
const passProps = {initialValue};
|
||||
const options = {
|
||||
topBar: {
|
||||
visible: false,
|
||||
height: 0,
|
||||
},
|
||||
};
|
||||
|
||||
showModal(name, title, passProps, options);
|
||||
}
|
||||
|
||||
export async function dismissModal(options = {}) {
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
|
||||
try {
|
||||
await Navigation.dismissModal(componentId, options);
|
||||
} catch (error) {
|
||||
// RNN returns a promise rejection if there is no modal to
|
||||
// dismiss. We'll do nothing in this case.
|
||||
}
|
||||
}
|
||||
|
||||
export async function dismissAllModals(options = {}) {
|
||||
try {
|
||||
await Navigation.dismissAllModals(options);
|
||||
} catch (error) {
|
||||
// RNN returns a promise rejection if there are no modals to
|
||||
// dismiss. We'll do nothing in this case.
|
||||
}
|
||||
}
|
||||
|
||||
export function peek(name, passProps = {}, options = {}) {
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
const defaultOptions = {
|
||||
preview: {
|
||||
commit: false,
|
||||
},
|
||||
};
|
||||
|
||||
Navigation.push(componentId, {
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function setButtons(componentId, buttons = {leftButtons: [], rightButtons: []}) {
|
||||
const options = {
|
||||
topBar: {
|
||||
...buttons,
|
||||
},
|
||||
};
|
||||
|
||||
mergeNavigationOptions(componentId, options);
|
||||
}
|
||||
|
||||
export function mergeNavigationOptions(componentId, options) {
|
||||
Navigation.mergeOptions(componentId, options);
|
||||
}
|
||||
|
||||
export function showOverlay(name, passProps, options = {}) {
|
||||
const defaultOptions = {
|
||||
overlay: {
|
||||
interceptTouchOutside: false,
|
||||
},
|
||||
};
|
||||
|
||||
Navigation.showOverlay({
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function dismissOverlay(componentId) {
|
||||
try {
|
||||
await Navigation.dismissOverlay(componentId);
|
||||
} catch (error) {
|
||||
// RNN returns a promise rejection if there is no modal with
|
||||
// this componentId to dismiss. We'll do nothing in this case.
|
||||
}
|
||||
}
|
||||
473
app/actions/navigation/index.test.js
Normal file
473
app/actions/navigation/index.test.js
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Platform} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import merge from 'deepmerge';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import * as NavigationActions from 'app/actions/navigation';
|
||||
|
||||
jest.unmock('app/actions/navigation');
|
||||
jest.mock('app/store/ephemeral_store', () => ({
|
||||
getNavigationTopComponentId: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('app/actions/navigation', () => {
|
||||
const topComponentId = 'top-component-id';
|
||||
const name = 'name';
|
||||
const title = 'title';
|
||||
const theme = Preferences.THEMES.default;
|
||||
const passProps = {
|
||||
testProp: 'prop',
|
||||
};
|
||||
const options = {
|
||||
testOption: 'test',
|
||||
};
|
||||
EphemeralStore.getNavigationTopComponentId.mockReturnValue(topComponentId);
|
||||
|
||||
test('resetToChannel should call Navigation.setRoot', () => {
|
||||
const setRoot = jest.spyOn(Navigation, 'setRoot');
|
||||
|
||||
const expectedLayout = {
|
||||
root: {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name: 'Channel',
|
||||
passProps,
|
||||
options: {
|
||||
layout: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
visible: false,
|
||||
height: 0,
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
NavigationActions.resetToChannel(passProps);
|
||||
expect(setRoot).toHaveBeenCalledWith(expectedLayout);
|
||||
});
|
||||
|
||||
test('resetToSelectServer should call Navigation.setRoot', () => {
|
||||
const setRoot = jest.spyOn(Navigation, 'setRoot');
|
||||
|
||||
const allowOtherServers = false;
|
||||
const expectedLayout = {
|
||||
root: {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name: 'SelectServer',
|
||||
passProps: {
|
||||
allowOtherServers,
|
||||
},
|
||||
options: {
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
visible: false,
|
||||
height: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
NavigationActions.resetToSelectServer(allowOtherServers);
|
||||
expect(setRoot).toHaveBeenCalledWith(expectedLayout);
|
||||
});
|
||||
|
||||
test('resetToTeams should call Navigation.setRoot', () => {
|
||||
const setRoot = jest.spyOn(Navigation, 'setRoot');
|
||||
|
||||
const defaultOptions = {
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
visible: true,
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: title,
|
||||
},
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const expectedLayout = {
|
||||
root: {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
NavigationActions.resetToTeams(name, title, passProps, options);
|
||||
expect(setRoot).toHaveBeenCalledWith(expectedLayout);
|
||||
});
|
||||
|
||||
test('goToScreen should call Navigation.push', () => {
|
||||
const push = jest.spyOn(Navigation, 'push');
|
||||
|
||||
const defaultOptions = {
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
topBar: {
|
||||
animate: true,
|
||||
visible: true,
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: title,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const expectedLayout = {
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
};
|
||||
|
||||
NavigationActions.goToScreen(name, title, passProps, options);
|
||||
expect(push).toHaveBeenCalledWith(topComponentId, expectedLayout);
|
||||
});
|
||||
|
||||
test('popTopScreen should call Navigation.pop', () => {
|
||||
const pop = jest.spyOn(Navigation, 'pop');
|
||||
|
||||
NavigationActions.popTopScreen();
|
||||
expect(pop).toHaveBeenCalledWith(topComponentId);
|
||||
|
||||
const otherComponentId = `other-${topComponentId}`;
|
||||
NavigationActions.popTopScreen(otherComponentId);
|
||||
expect(pop).toHaveBeenCalledWith(otherComponentId);
|
||||
});
|
||||
|
||||
test('popToRoot should call Navigation.popToRoot', async () => {
|
||||
const popToRoot = jest.spyOn(Navigation, 'popToRoot');
|
||||
|
||||
await NavigationActions.popToRoot();
|
||||
expect(popToRoot).toHaveBeenCalledWith(topComponentId);
|
||||
});
|
||||
|
||||
test('showModal should call Navigation.showModal', () => {
|
||||
const showModal = jest.spyOn(Navigation, 'showModal');
|
||||
|
||||
const defaultOptions = {
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
animate: true,
|
||||
visible: true,
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: title,
|
||||
},
|
||||
leftButtonColor: theme.sidebarHeaderTextColor,
|
||||
rightButtonColor: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
};
|
||||
|
||||
const expectedLayout = {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
NavigationActions.showModal(name, title, passProps, options);
|
||||
expect(showModal).toHaveBeenCalledWith(expectedLayout);
|
||||
});
|
||||
|
||||
test('showModalOverCurrentContext should call Navigation.showModal', () => {
|
||||
const showModal = jest.spyOn(Navigation, 'showModal');
|
||||
|
||||
const animationsEnabled = (Platform.OS === 'android').toString();
|
||||
const showModalOverCurrentContextTitle = '';
|
||||
const showModalOverCurrentContextOptions = {
|
||||
modalPresentationStyle: 'overCurrentContext',
|
||||
layout: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
topBar: {
|
||||
visible: false,
|
||||
height: 0,
|
||||
},
|
||||
animations: {
|
||||
showModal: {
|
||||
enabled: animationsEnabled,
|
||||
alpha: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
duration: 250,
|
||||
},
|
||||
},
|
||||
dismissModal: {
|
||||
enabled: animationsEnabled,
|
||||
alpha: {
|
||||
from: 1,
|
||||
to: 0,
|
||||
duration: 250,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const showModalOptions = {
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
animate: true,
|
||||
visible: true,
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: showModalOverCurrentContextTitle,
|
||||
},
|
||||
leftButtonColor: theme.sidebarHeaderTextColor,
|
||||
rightButtonColor: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
};
|
||||
const defaultOptions = merge(showModalOverCurrentContextOptions, options);
|
||||
|
||||
const expectedLayout = {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(showModalOptions, defaultOptions),
|
||||
},
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
NavigationActions.showModalOverCurrentContext(name, passProps, options);
|
||||
expect(showModal).toHaveBeenCalledWith(expectedLayout);
|
||||
});
|
||||
|
||||
test('showSearchModal should call Navigation.showModal', () => {
|
||||
const showModal = jest.spyOn(Navigation, 'showModal');
|
||||
|
||||
const showSearchModalName = 'Search';
|
||||
const showSearchModalTitle = '';
|
||||
const initialValue = 'initial-value';
|
||||
const showSearchModalPassProps = {initialValue};
|
||||
const showSearchModalOptions = {
|
||||
topBar: {
|
||||
visible: false,
|
||||
height: 0,
|
||||
},
|
||||
};
|
||||
const defaultOptions = {
|
||||
layout: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
statusBar: {
|
||||
visible: true,
|
||||
},
|
||||
topBar: {
|
||||
animate: true,
|
||||
visible: true,
|
||||
backButton: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
title: '',
|
||||
},
|
||||
background: {
|
||||
color: theme.sidebarHeaderBg,
|
||||
},
|
||||
title: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: showSearchModalTitle,
|
||||
},
|
||||
leftButtonColor: theme.sidebarHeaderTextColor,
|
||||
rightButtonColor: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
};
|
||||
|
||||
const expectedLayout = {
|
||||
stack: {
|
||||
children: [{
|
||||
component: {
|
||||
name: showSearchModalName,
|
||||
passProps: showSearchModalPassProps,
|
||||
options: merge(defaultOptions, showSearchModalOptions),
|
||||
},
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
NavigationActions.showSearchModal(initialValue);
|
||||
expect(showModal).toHaveBeenCalledWith(expectedLayout);
|
||||
});
|
||||
|
||||
test('dismissModal should call Navigation.dismissModal', async () => {
|
||||
const dismissModal = jest.spyOn(Navigation, 'dismissModal');
|
||||
|
||||
await NavigationActions.dismissModal(options);
|
||||
expect(dismissModal).toHaveBeenCalledWith(topComponentId, options);
|
||||
});
|
||||
|
||||
test('dismissAllModals should call Navigation.dismissAllModals', async () => {
|
||||
const dismissAllModals = jest.spyOn(Navigation, 'dismissAllModals');
|
||||
|
||||
await NavigationActions.dismissAllModals(options);
|
||||
expect(dismissAllModals).toHaveBeenCalledWith(options);
|
||||
});
|
||||
|
||||
test('peek should call Navigation.push', async () => {
|
||||
const push = jest.spyOn(Navigation, 'push');
|
||||
|
||||
const defaultOptions = {
|
||||
preview: {
|
||||
commit: false,
|
||||
},
|
||||
};
|
||||
|
||||
const expectedLayout = {
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
};
|
||||
|
||||
await NavigationActions.peek(name, passProps, options);
|
||||
expect(push).toHaveBeenCalledWith(topComponentId, expectedLayout);
|
||||
});
|
||||
|
||||
test('mergeNavigationOptions should call Navigation.mergeOptions', () => {
|
||||
const mergeOptions = jest.spyOn(Navigation, 'mergeOptions');
|
||||
|
||||
NavigationActions.mergeNavigationOptions(topComponentId, options);
|
||||
expect(mergeOptions).toHaveBeenCalledWith(topComponentId, options);
|
||||
});
|
||||
|
||||
test('setButtons should call Navigation.mergeOptions', () => {
|
||||
const mergeOptions = jest.spyOn(Navigation, 'mergeOptions');
|
||||
|
||||
const buttons = {
|
||||
leftButtons: ['left-button'],
|
||||
rightButtons: ['right-button'],
|
||||
};
|
||||
const setButtonsOptions = {
|
||||
topBar: {
|
||||
...buttons,
|
||||
},
|
||||
};
|
||||
|
||||
NavigationActions.setButtons(topComponentId, buttons);
|
||||
expect(mergeOptions).toHaveBeenCalledWith(topComponentId, setButtonsOptions);
|
||||
});
|
||||
|
||||
test('showOverlay should call Navigation.showOverlay', () => {
|
||||
const showOverlay = jest.spyOn(Navigation, 'showOverlay');
|
||||
|
||||
const defaultOptions = {
|
||||
overlay: {
|
||||
interceptTouchOutside: false,
|
||||
},
|
||||
};
|
||||
|
||||
const expectedLayout = {
|
||||
component: {
|
||||
name,
|
||||
passProps,
|
||||
options: merge(defaultOptions, options),
|
||||
},
|
||||
};
|
||||
|
||||
NavigationActions.showOverlay(name, passProps, options);
|
||||
expect(showOverlay).toHaveBeenCalledWith(expectedLayout);
|
||||
});
|
||||
|
||||
test('dismissOverlay should call Navigation.dismissOverlay', async () => {
|
||||
const dismissOverlay = jest.spyOn(Navigation, 'dismissOverlay');
|
||||
|
||||
await NavigationActions.dismissOverlay(topComponentId);
|
||||
expect(dismissOverlay).toHaveBeenCalledWith(topComponentId);
|
||||
});
|
||||
});
|
||||
|
|
@ -10,7 +10,8 @@ import {
|
|||
fetchMyChannelsAndMembers,
|
||||
getChannelByNameAndTeamName,
|
||||
markChannelAsRead,
|
||||
leaveChannel as serviceLeaveChannel, markChannelAsViewed,
|
||||
markChannelAsViewed,
|
||||
leaveChannel as serviceLeaveChannel,
|
||||
selectChannel,
|
||||
getChannelStats,
|
||||
} from 'mattermost-redux/actions/channels';
|
||||
|
|
|
|||
|
|
@ -20,6 +20,23 @@ jest.mock('mattermost-redux/selectors/entities/channels', () => ({
|
|||
getMyChannelMember: () => ({data: {member: {}}}),
|
||||
}));
|
||||
|
||||
jest.mock('mattermost-redux/actions/channels', () => {
|
||||
const channelActions = require.requireActual('mattermost-redux/actions/channels');
|
||||
return {
|
||||
...channelActions,
|
||||
markChannelAsRead: jest.fn(),
|
||||
markChannelAsViewed: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('mattermost-redux/selectors/entities/teams', () => {
|
||||
const teamSelectors = require.requireActual('mattermost-redux/selectors/entities/teams');
|
||||
return {
|
||||
...teamSelectors,
|
||||
getTeamByName: jest.fn(() => ({name: 'current-team-name'})),
|
||||
};
|
||||
});
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
|
||||
describe('Actions.Views.Channel', () => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,20 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`profile_picture_button should match snapshot 1`] = `
|
||||
<Connect(AttachmentButton)
|
||||
<AttachmentButton
|
||||
blurTextBox={[MockFunction]}
|
||||
browseFileTypes="public.item"
|
||||
canBrowseFiles={true}
|
||||
canBrowsePhotoLibrary={true}
|
||||
canBrowseVideoLibrary={true}
|
||||
canTakePhoto={true}
|
||||
canTakeVideo={true}
|
||||
extraOptions={
|
||||
Array [
|
||||
null,
|
||||
]
|
||||
}
|
||||
maxFileCount={5}
|
||||
maxFileSize={20971520}
|
||||
theme={
|
||||
Object {
|
||||
|
|
@ -38,5 +45,6 @@ exports[`profile_picture_button should match snapshot 1`] = `
|
|||
}
|
||||
}
|
||||
uploadFiles={[MockFunction]}
|
||||
validMimeTypes={Array []}
|
||||
/>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -11,16 +11,15 @@ import {
|
|||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import RemoveMarkdown from 'app/components/remove_markdown';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
export default class AnnouncementBanner extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
bannerColor: PropTypes.string,
|
||||
bannerDismissed: PropTypes.bool,
|
||||
bannerEnabled: PropTypes.bool,
|
||||
|
|
@ -55,7 +54,6 @@ export default class AnnouncementBanner extends PureComponent {
|
|||
}
|
||||
|
||||
handlePress = () => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
|
||||
const screen = 'ExpandedAnnouncementBanner';
|
||||
|
|
@ -64,7 +62,7 @@ export default class AnnouncementBanner extends PureComponent {
|
|||
defaultMessage: 'Announcement',
|
||||
});
|
||||
|
||||
actions.goToScreen(screen, title);
|
||||
goToScreen(screen, title);
|
||||
};
|
||||
|
||||
toggleBanner = (show = true) => {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,6 @@ jest.useFakeTimers();
|
|||
|
||||
describe('AnnouncementBanner', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
goToScreen: jest.fn(),
|
||||
},
|
||||
bannerColor: '#ddd',
|
||||
bannerDismissed: false,
|
||||
bannerEnabled: true,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import AnnouncementBanner from './announcement_banner';
|
||||
|
|
@ -28,12 +26,4 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AnnouncementBanner);
|
||||
export default connect(mapStateToProps)(AnnouncementBanner);
|
||||
|
|
|
|||
|
|
@ -11,12 +11,10 @@ import {displayUsername} from 'mattermost-redux/utils/user_utils';
|
|||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
export default class AtMention extends React.PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
isSearchResult: PropTypes.bool,
|
||||
mentionName: PropTypes.string.isRequired,
|
||||
mentionStyle: CustomPropTypes.Style,
|
||||
|
|
@ -50,7 +48,6 @@ export default class AtMention extends React.PureComponent {
|
|||
}
|
||||
|
||||
goToUserProfile = () => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'UserProfile';
|
||||
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
|
||||
|
|
@ -58,7 +55,7 @@ export default class AtMention extends React.PureComponent {
|
|||
userId: this.state.user.id,
|
||||
};
|
||||
|
||||
actions.goToScreen(screen, title, passProps);
|
||||
goToScreen(screen, title, passProps);
|
||||
};
|
||||
|
||||
getUserDetailsFromMentionName(props) {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getUsersByUsername} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
import AtMention from './at_mention';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
@ -20,12 +17,4 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AtMention);
|
||||
export default connect(mapStateToProps)(AtMention);
|
||||
|
|
|
|||
|
|
@ -1,538 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Alert,
|
||||
NativeModules,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import AndroidOpenSettings from 'react-native-android-open-settings';
|
||||
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import {DocumentPicker} from 'react-native-document-picker';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import Permissions from 'react-native-permissions';
|
||||
|
||||
import {lookupMimeType} from 'mattermost-redux/utils/file_utils';
|
||||
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {PermissionTypes} from 'app/constants';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
const ShareExtension = NativeModules.MattermostShare;
|
||||
|
||||
export default class AttachmentButton extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
blurTextBox: PropTypes.func.isRequired,
|
||||
browseFileTypes: PropTypes.string,
|
||||
validMimeTypes: PropTypes.array,
|
||||
canBrowseFiles: PropTypes.bool,
|
||||
canBrowsePhotoLibrary: PropTypes.bool,
|
||||
canBrowseVideoLibrary: PropTypes.bool,
|
||||
canTakePhoto: PropTypes.bool,
|
||||
canTakeVideo: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
fileCount: PropTypes.number,
|
||||
maxFileCount: PropTypes.number.isRequired,
|
||||
maxFileSize: PropTypes.number.isRequired,
|
||||
onShowFileMaxWarning: PropTypes.func,
|
||||
onShowFileSizeWarning: PropTypes.func,
|
||||
onShowUnsupportedMimeTypeWarning: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired,
|
||||
uploadFiles: PropTypes.func.isRequired,
|
||||
wrapper: PropTypes.bool,
|
||||
extraOptions: PropTypes.arrayOf(PropTypes.object),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
browseFileTypes: Platform.OS === 'ios' ? 'public.item' : '*/*',
|
||||
validMimeTypes: [],
|
||||
canBrowseFiles: true,
|
||||
canBrowsePhotoLibrary: true,
|
||||
canBrowseVideoLibrary: true,
|
||||
canTakePhoto: true,
|
||||
canTakeVideo: true,
|
||||
maxFileCount: 5,
|
||||
extraOptions: null,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
getPermissionDeniedMessage = (source, mediaType = '') => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const applicationName = DeviceInfo.getApplicationName();
|
||||
switch (source) {
|
||||
case 'camera': {
|
||||
if (mediaType === 'video') {
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.camera_video_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your camera',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.camera_video_permission_denied_description',
|
||||
defaultMessage: 'Take videos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.camera_photo_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your camera',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.camera_photo_permission_denied_description',
|
||||
defaultMessage: 'Take photos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
case 'storage':
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.storage_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your files',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.storage_permission_denied_description',
|
||||
defaultMessage: 'Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.',
|
||||
}),
|
||||
};
|
||||
case 'video':
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.android.videos_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your videos',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.android.videos_permission_denied_description',
|
||||
defaultMessage: 'Upload videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your video library.',
|
||||
}),
|
||||
};
|
||||
case 'photo':
|
||||
default: {
|
||||
if (Platform.OS === 'android') {
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.android.photos_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your photos',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.android.photos_permission_denied_description',
|
||||
defaultMessage: 'Upload photos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo library.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.ios.photos_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your photos',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.ios.photos_permission_denied_description',
|
||||
defaultMessage: 'Upload photos and videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo and video library.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attachPhotoFromCamera = () => {
|
||||
return this.attachFileFromCamera('camera', 'photo');
|
||||
};
|
||||
|
||||
attachFileFromCamera = async (source, mediaType) => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {title, text} = this.getPermissionDeniedMessage('camera', mediaType);
|
||||
const options = {
|
||||
quality: 0.8,
|
||||
videoQuality: 'high',
|
||||
noData: true,
|
||||
mediaType,
|
||||
storageOptions: {
|
||||
cameraRoll: true,
|
||||
waitUntilSaved: true,
|
||||
},
|
||||
permissionDenied: {
|
||||
title,
|
||||
text,
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
|
||||
},
|
||||
};
|
||||
|
||||
const hasCameraPermission = await this.hasPhotoPermission(source, mediaType);
|
||||
|
||||
if (hasCameraPermission) {
|
||||
ImagePicker.launchCamera(options, (response) => {
|
||||
if (response.error || response.didCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uploadFiles([response]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
attachFileFromLibrary = async () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {title, text} = this.getPermissionDeniedMessage('photo');
|
||||
const options = {
|
||||
quality: 0.8,
|
||||
noData: true,
|
||||
permissionDenied: {
|
||||
title,
|
||||
text,
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
|
||||
},
|
||||
};
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
options.mediaType = 'mixed';
|
||||
}
|
||||
|
||||
const hasPhotoPermission = await this.hasPhotoPermission('photo');
|
||||
|
||||
if (hasPhotoPermission) {
|
||||
ImagePicker.launchImageLibrary(options, (response) => {
|
||||
if (response.error || response.didCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uploadFiles([response]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
attachVideoFromCamera = () => {
|
||||
return this.attachFileFromCamera('camera', 'video');
|
||||
};
|
||||
|
||||
attachVideoFromLibraryAndroid = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {title, text} = this.getPermissionDeniedMessage('video');
|
||||
const options = {
|
||||
videoQuality: 'high',
|
||||
mediaType: 'video',
|
||||
noData: true,
|
||||
permissionDenied: {
|
||||
title,
|
||||
text,
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
|
||||
},
|
||||
};
|
||||
|
||||
ImagePicker.launchImageLibrary(options, (response) => {
|
||||
if (response.error || response.didCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uploadFiles([response]);
|
||||
});
|
||||
};
|
||||
|
||||
attachFileFromFiles = async () => {
|
||||
const {browseFileTypes} = this.props;
|
||||
const hasPermission = await this.hasStoragePermission();
|
||||
|
||||
if (hasPermission) {
|
||||
DocumentPicker.show({
|
||||
filetype: [browseFileTypes],
|
||||
}, async (error, res) => {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
// For android we need to retrieve the realPath in case the file being imported is from the cloud
|
||||
const newUri = await ShareExtension.getFilePath(res.uri);
|
||||
if (newUri.filePath) {
|
||||
res.uri = newUri.filePath;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode file uri to get the actual path
|
||||
res.uri = decodeURIComponent(res.uri);
|
||||
|
||||
this.uploadFiles([res]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
hasPhotoPermission = async (source, mediaType = '') => {
|
||||
if (Platform.OS === 'ios') {
|
||||
const {formatMessage} = this.context.intl;
|
||||
let permissionRequest;
|
||||
const targetSource = source || 'photo';
|
||||
const hasPermissionToStorage = await Permissions.check(targetSource);
|
||||
|
||||
switch (hasPermissionToStorage) {
|
||||
case PermissionTypes.UNDETERMINED:
|
||||
permissionRequest = await Permissions.request(targetSource);
|
||||
if (permissionRequest !== PermissionTypes.AUTHORIZED) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case PermissionTypes.DENIED: {
|
||||
const canOpenSettings = await Permissions.canOpenSettings();
|
||||
let grantOption = null;
|
||||
if (canOpenSettings) {
|
||||
grantOption = {
|
||||
text: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
onPress: () => Permissions.openSettings(),
|
||||
};
|
||||
}
|
||||
|
||||
const {title, text} = this.getPermissionDeniedMessage(source, mediaType);
|
||||
|
||||
Alert.alert(
|
||||
title,
|
||||
text,
|
||||
[
|
||||
grantOption,
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'mobile.permission_denied_dismiss',
|
||||
defaultMessage: 'Don\'t Allow',
|
||||
}),
|
||||
},
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
hasStoragePermission = async () => {
|
||||
if (Platform.OS === 'android') {
|
||||
const {formatMessage} = this.context.intl;
|
||||
let permissionRequest;
|
||||
const hasPermissionToStorage = await Permissions.check('storage');
|
||||
|
||||
switch (hasPermissionToStorage) {
|
||||
case PermissionTypes.UNDETERMINED:
|
||||
permissionRequest = await Permissions.request('storage');
|
||||
if (permissionRequest !== PermissionTypes.AUTHORIZED) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case PermissionTypes.DENIED: {
|
||||
const {title, text} = this.getPermissionDeniedMessage('storage');
|
||||
|
||||
Alert.alert(
|
||||
title,
|
||||
text,
|
||||
[
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'mobile.permission_denied_dismiss',
|
||||
defaultMessage: 'Don\'t Allow',
|
||||
}),
|
||||
},
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
onPress: () => AndroidOpenSettings.appDetailsSettings(),
|
||||
},
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
uploadFiles = async (files) => {
|
||||
const file = files[0];
|
||||
if (!file.fileSize | !file.fileName) {
|
||||
const path = (file.path || file.uri).replace('file://', '');
|
||||
const fileInfo = await RNFetchBlob.fs.stat(path);
|
||||
file.fileSize = fileInfo.size;
|
||||
file.fileName = fileInfo.filename;
|
||||
}
|
||||
|
||||
if (!file.type) {
|
||||
file.type = lookupMimeType(file.fileName);
|
||||
}
|
||||
|
||||
const {validMimeTypes} = this.props;
|
||||
if (validMimeTypes.length && !validMimeTypes.includes(file.type)) {
|
||||
this.props.onShowUnsupportedMimeTypeWarning();
|
||||
} else if (file.fileSize > this.props.maxFileSize) {
|
||||
this.props.onShowFileSizeWarning(file.fileName);
|
||||
} else {
|
||||
this.props.uploadFiles(files);
|
||||
}
|
||||
};
|
||||
|
||||
showFileAttachmentOptions = () => {
|
||||
const {
|
||||
canBrowseFiles,
|
||||
canBrowsePhotoLibrary,
|
||||
canBrowseVideoLibrary,
|
||||
canTakePhoto,
|
||||
canTakeVideo,
|
||||
fileCount,
|
||||
maxFileCount,
|
||||
onShowFileMaxWarning,
|
||||
extraOptions,
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
if (fileCount === maxFileCount) {
|
||||
onShowFileMaxWarning();
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.blurTextBox();
|
||||
const items = [];
|
||||
|
||||
if (canTakePhoto) {
|
||||
items.push({
|
||||
action: this.attachPhotoFromCamera,
|
||||
text: {
|
||||
id: t('mobile.file_upload.camera_photo'),
|
||||
defaultMessage: 'Take Photo',
|
||||
},
|
||||
icon: 'camera',
|
||||
});
|
||||
}
|
||||
|
||||
if (canTakeVideo) {
|
||||
items.push({
|
||||
action: this.attachVideoFromCamera,
|
||||
text: {
|
||||
id: t('mobile.file_upload.camera_video'),
|
||||
defaultMessage: 'Take Video',
|
||||
},
|
||||
icon: 'video-camera',
|
||||
});
|
||||
}
|
||||
|
||||
if (canBrowsePhotoLibrary) {
|
||||
items.push({
|
||||
action: this.attachFileFromLibrary,
|
||||
text: {
|
||||
id: t('mobile.file_upload.library'),
|
||||
defaultMessage: 'Photo Library',
|
||||
},
|
||||
icon: 'photo',
|
||||
});
|
||||
}
|
||||
|
||||
if (canBrowseVideoLibrary && Platform.OS === 'android') {
|
||||
items.push({
|
||||
action: this.attachVideoFromLibraryAndroid,
|
||||
text: {
|
||||
id: t('mobile.file_upload.video'),
|
||||
defaultMessage: 'Video Library',
|
||||
},
|
||||
icon: 'file-video-o',
|
||||
});
|
||||
}
|
||||
|
||||
if (canBrowseFiles) {
|
||||
items.push({
|
||||
action: this.attachFileFromFiles,
|
||||
text: {
|
||||
id: t('mobile.file_upload.browse'),
|
||||
defaultMessage: 'Browse Files',
|
||||
},
|
||||
icon: 'file',
|
||||
});
|
||||
}
|
||||
|
||||
if (extraOptions) {
|
||||
extraOptions.forEach((option) => {
|
||||
if (option !== null) {
|
||||
items.push(option);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
actions.showModalOverCurrentContext('OptionsModal', {items});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme, wrapper, children} = this.props;
|
||||
|
||||
if (wrapper) {
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.showFileAttachmentOptions}
|
||||
type={'opacity'}
|
||||
>
|
||||
{children}
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.showFileAttachmentOptions}
|
||||
style={style.buttonContainer}
|
||||
type={'opacity'}
|
||||
>
|
||||
<Icon
|
||||
size={30}
|
||||
style={style.attachIcon}
|
||||
color={changeOpacity(theme.centerChannelColor, 0.9)}
|
||||
name='md-add'
|
||||
/>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
attachIcon: {
|
||||
marginTop: Platform.select({
|
||||
ios: 2,
|
||||
android: 0,
|
||||
}),
|
||||
},
|
||||
buttonContainer: {
|
||||
height: Platform.select({
|
||||
ios: 34,
|
||||
android: 36,
|
||||
}),
|
||||
width: 45,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
|
|
@ -1,19 +1,535 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
Alert,
|
||||
NativeModules,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
} from 'react-native';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import AndroidOpenSettings from 'react-native-android-open-settings';
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
import Icon from 'react-native-vector-icons/Ionicons';
|
||||
import {DocumentPicker} from 'react-native-document-picker';
|
||||
import ImagePicker from 'react-native-image-picker';
|
||||
import Permissions from 'react-native-permissions';
|
||||
|
||||
import {lookupMimeType} from 'mattermost-redux/utils/file_utils';
|
||||
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {PermissionTypes} from 'app/constants';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
|
||||
import AttachmentButton from './attachment_button';
|
||||
const ShareExtension = NativeModules.MattermostShare;
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
export default class AttachmentButton extends PureComponent {
|
||||
static propTypes = {
|
||||
blurTextBox: PropTypes.func.isRequired,
|
||||
browseFileTypes: PropTypes.string,
|
||||
validMimeTypes: PropTypes.array,
|
||||
canBrowseFiles: PropTypes.bool,
|
||||
canBrowsePhotoLibrary: PropTypes.bool,
|
||||
canBrowseVideoLibrary: PropTypes.bool,
|
||||
canTakePhoto: PropTypes.bool,
|
||||
canTakeVideo: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
fileCount: PropTypes.number,
|
||||
maxFileCount: PropTypes.number.isRequired,
|
||||
maxFileSize: PropTypes.number.isRequired,
|
||||
onShowFileMaxWarning: PropTypes.func,
|
||||
onShowFileSizeWarning: PropTypes.func,
|
||||
onShowUnsupportedMimeTypeWarning: PropTypes.func,
|
||||
theme: PropTypes.object.isRequired,
|
||||
uploadFiles: PropTypes.func.isRequired,
|
||||
wrapper: PropTypes.bool,
|
||||
extraOptions: PropTypes.arrayOf(PropTypes.object),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
browseFileTypes: Platform.OS === 'ios' ? 'public.item' : '*/*',
|
||||
validMimeTypes: [],
|
||||
canBrowseFiles: true,
|
||||
canBrowsePhotoLibrary: true,
|
||||
canBrowseVideoLibrary: true,
|
||||
canTakePhoto: true,
|
||||
canTakeVideo: true,
|
||||
maxFileCount: 5,
|
||||
extraOptions: null,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
getPermissionDeniedMessage = (source, mediaType = '') => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const applicationName = DeviceInfo.getApplicationName();
|
||||
switch (source) {
|
||||
case 'camera': {
|
||||
if (mediaType === 'video') {
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.camera_video_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your camera',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.camera_video_permission_denied_description',
|
||||
defaultMessage: 'Take videos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.camera_photo_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your camera',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.camera_photo_permission_denied_description',
|
||||
defaultMessage: 'Take photos and upload them to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your camera.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
case 'storage':
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.storage_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your files',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.storage_permission_denied_description',
|
||||
defaultMessage: 'Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.',
|
||||
}),
|
||||
};
|
||||
case 'video':
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.android.videos_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your videos',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.android.videos_permission_denied_description',
|
||||
defaultMessage: 'Upload videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your video library.',
|
||||
}),
|
||||
};
|
||||
case 'photo':
|
||||
default: {
|
||||
if (Platform.OS === 'android') {
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.android.photos_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your photos',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.android.photos_permission_denied_description',
|
||||
defaultMessage: 'Upload photos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo library.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: formatMessage({
|
||||
id: 'mobile.ios.photos_permission_denied_title',
|
||||
defaultMessage: '{applicationName} would like to access your photos',
|
||||
}, {applicationName}),
|
||||
text: formatMessage({
|
||||
id: 'mobile.ios.photos_permission_denied_description',
|
||||
defaultMessage: 'Upload photos and videos to your Mattermost instance or save them to your device. Open Settings to grant Mattermost Read and Write access to your photo and video library.',
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attachPhotoFromCamera = () => {
|
||||
return this.attachFileFromCamera('camera', 'photo');
|
||||
};
|
||||
|
||||
attachFileFromCamera = async (source, mediaType) => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {title, text} = this.getPermissionDeniedMessage('camera', mediaType);
|
||||
const options = {
|
||||
quality: 0.8,
|
||||
videoQuality: 'high',
|
||||
noData: true,
|
||||
mediaType,
|
||||
storageOptions: {
|
||||
cameraRoll: true,
|
||||
waitUntilSaved: true,
|
||||
},
|
||||
permissionDenied: {
|
||||
title,
|
||||
text,
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
|
||||
},
|
||||
};
|
||||
|
||||
const hasCameraPermission = await this.hasPhotoPermission(source, mediaType);
|
||||
|
||||
if (hasCameraPermission) {
|
||||
ImagePicker.launchCamera(options, (response) => {
|
||||
if (response.error || response.didCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uploadFiles([response]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
attachFileFromLibrary = async () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {title, text} = this.getPermissionDeniedMessage('photo');
|
||||
const options = {
|
||||
quality: 0.8,
|
||||
noData: true,
|
||||
permissionDenied: {
|
||||
title,
|
||||
text,
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
|
||||
},
|
||||
};
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
options.mediaType = 'mixed';
|
||||
}
|
||||
|
||||
const hasPhotoPermission = await this.hasPhotoPermission('photo');
|
||||
|
||||
if (hasPhotoPermission) {
|
||||
ImagePicker.launchImageLibrary(options, (response) => {
|
||||
if (response.error || response.didCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uploadFiles([response]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
attachVideoFromCamera = () => {
|
||||
return this.attachFileFromCamera('camera', 'video');
|
||||
};
|
||||
|
||||
attachVideoFromLibraryAndroid = () => {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {title, text} = this.getPermissionDeniedMessage('video');
|
||||
const options = {
|
||||
videoQuality: 'high',
|
||||
mediaType: 'video',
|
||||
noData: true,
|
||||
permissionDenied: {
|
||||
title,
|
||||
text,
|
||||
reTryTitle: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
okTitle: formatMessage({id: 'mobile.permission_denied_dismiss', defaultMessage: 'Don\'t Allow'}),
|
||||
},
|
||||
};
|
||||
|
||||
ImagePicker.launchImageLibrary(options, (response) => {
|
||||
if (response.error || response.didCancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.uploadFiles([response]);
|
||||
});
|
||||
};
|
||||
|
||||
attachFileFromFiles = async () => {
|
||||
const {browseFileTypes} = this.props;
|
||||
const hasPermission = await this.hasStoragePermission();
|
||||
|
||||
if (hasPermission) {
|
||||
DocumentPicker.show({
|
||||
filetype: [browseFileTypes],
|
||||
}, async (error, res) => {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
// For android we need to retrieve the realPath in case the file being imported is from the cloud
|
||||
const newUri = await ShareExtension.getFilePath(res.uri);
|
||||
if (newUri.filePath) {
|
||||
res.uri = newUri.filePath;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode file uri to get the actual path
|
||||
res.uri = decodeURIComponent(res.uri);
|
||||
|
||||
this.uploadFiles([res]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
hasPhotoPermission = async (source, mediaType = '') => {
|
||||
if (Platform.OS === 'ios') {
|
||||
const {formatMessage} = this.context.intl;
|
||||
let permissionRequest;
|
||||
const targetSource = source || 'photo';
|
||||
const hasPermissionToStorage = await Permissions.check(targetSource);
|
||||
|
||||
switch (hasPermissionToStorage) {
|
||||
case PermissionTypes.UNDETERMINED:
|
||||
permissionRequest = await Permissions.request(targetSource);
|
||||
if (permissionRequest !== PermissionTypes.AUTHORIZED) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case PermissionTypes.DENIED: {
|
||||
const canOpenSettings = await Permissions.canOpenSettings();
|
||||
let grantOption = null;
|
||||
if (canOpenSettings) {
|
||||
grantOption = {
|
||||
text: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
onPress: () => Permissions.openSettings(),
|
||||
};
|
||||
}
|
||||
|
||||
const {title, text} = this.getPermissionDeniedMessage(source, mediaType);
|
||||
|
||||
Alert.alert(
|
||||
title,
|
||||
text,
|
||||
[
|
||||
grantOption,
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'mobile.permission_denied_dismiss',
|
||||
defaultMessage: 'Don\'t Allow',
|
||||
}),
|
||||
},
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
hasStoragePermission = async () => {
|
||||
if (Platform.OS === 'android') {
|
||||
const {formatMessage} = this.context.intl;
|
||||
let permissionRequest;
|
||||
const hasPermissionToStorage = await Permissions.check('storage');
|
||||
|
||||
switch (hasPermissionToStorage) {
|
||||
case PermissionTypes.UNDETERMINED:
|
||||
permissionRequest = await Permissions.request('storage');
|
||||
if (permissionRequest !== PermissionTypes.AUTHORIZED) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case PermissionTypes.DENIED: {
|
||||
const {title, text} = this.getPermissionDeniedMessage('storage');
|
||||
|
||||
Alert.alert(
|
||||
title,
|
||||
text,
|
||||
[
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'mobile.permission_denied_dismiss',
|
||||
defaultMessage: 'Don\'t Allow',
|
||||
}),
|
||||
},
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'mobile.permission_denied_retry',
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
onPress: () => AndroidOpenSettings.appDetailsSettings(),
|
||||
},
|
||||
]
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
uploadFiles = async (files) => {
|
||||
const file = files[0];
|
||||
if (!file.fileSize | !file.fileName) {
|
||||
const path = (file.path || file.uri).replace('file://', '');
|
||||
const fileInfo = await RNFetchBlob.fs.stat(path);
|
||||
file.fileSize = fileInfo.size;
|
||||
file.fileName = fileInfo.filename;
|
||||
}
|
||||
|
||||
if (!file.type) {
|
||||
file.type = lookupMimeType(file.fileName);
|
||||
}
|
||||
|
||||
const {validMimeTypes} = this.props;
|
||||
if (validMimeTypes.length && !validMimeTypes.includes(file.type)) {
|
||||
this.props.onShowUnsupportedMimeTypeWarning();
|
||||
} else if (file.fileSize > this.props.maxFileSize) {
|
||||
this.props.onShowFileSizeWarning(file.fileName);
|
||||
} else {
|
||||
this.props.uploadFiles(files);
|
||||
}
|
||||
};
|
||||
|
||||
showFileAttachmentOptions = () => {
|
||||
const {
|
||||
canBrowseFiles,
|
||||
canBrowsePhotoLibrary,
|
||||
canBrowseVideoLibrary,
|
||||
canTakePhoto,
|
||||
canTakeVideo,
|
||||
fileCount,
|
||||
maxFileCount,
|
||||
onShowFileMaxWarning,
|
||||
extraOptions,
|
||||
} = this.props;
|
||||
|
||||
if (fileCount === maxFileCount) {
|
||||
onShowFileMaxWarning();
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.blurTextBox();
|
||||
const items = [];
|
||||
|
||||
if (canTakePhoto) {
|
||||
items.push({
|
||||
action: this.attachPhotoFromCamera,
|
||||
text: {
|
||||
id: t('mobile.file_upload.camera_photo'),
|
||||
defaultMessage: 'Take Photo',
|
||||
},
|
||||
icon: 'camera',
|
||||
});
|
||||
}
|
||||
|
||||
if (canTakeVideo) {
|
||||
items.push({
|
||||
action: this.attachVideoFromCamera,
|
||||
text: {
|
||||
id: t('mobile.file_upload.camera_video'),
|
||||
defaultMessage: 'Take Video',
|
||||
},
|
||||
icon: 'video-camera',
|
||||
});
|
||||
}
|
||||
|
||||
if (canBrowsePhotoLibrary) {
|
||||
items.push({
|
||||
action: this.attachFileFromLibrary,
|
||||
text: {
|
||||
id: t('mobile.file_upload.library'),
|
||||
defaultMessage: 'Photo Library',
|
||||
},
|
||||
icon: 'photo',
|
||||
});
|
||||
}
|
||||
|
||||
if (canBrowseVideoLibrary && Platform.OS === 'android') {
|
||||
items.push({
|
||||
action: this.attachVideoFromLibraryAndroid,
|
||||
text: {
|
||||
id: t('mobile.file_upload.video'),
|
||||
defaultMessage: 'Video Library',
|
||||
},
|
||||
icon: 'file-video-o',
|
||||
});
|
||||
}
|
||||
|
||||
if (canBrowseFiles) {
|
||||
items.push({
|
||||
action: this.attachFileFromFiles,
|
||||
text: {
|
||||
id: t('mobile.file_upload.browse'),
|
||||
defaultMessage: 'Browse Files',
|
||||
},
|
||||
icon: 'file',
|
||||
});
|
||||
}
|
||||
|
||||
if (extraOptions) {
|
||||
extraOptions.forEach((option) => {
|
||||
if (option !== null) {
|
||||
items.push(option);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showModalOverCurrentContext('OptionsModal', {items});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {theme, wrapper, children} = this.props;
|
||||
|
||||
if (wrapper) {
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.showFileAttachmentOptions}
|
||||
type={'opacity'}
|
||||
>
|
||||
{children}
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.showFileAttachmentOptions}
|
||||
style={style.buttonContainer}
|
||||
type={'opacity'}
|
||||
>
|
||||
<Icon
|
||||
size={30}
|
||||
style={style.attachIcon}
|
||||
color={changeOpacity(theme.centerChannelColor, 0.9)}
|
||||
name='md-add'
|
||||
/>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(AttachmentButton);
|
||||
const style = StyleSheet.create({
|
||||
attachIcon: {
|
||||
marginTop: Platform.select({
|
||||
ios: 2,
|
||||
android: 0,
|
||||
}),
|
||||
},
|
||||
buttonContainer: {
|
||||
height: Platform.select({
|
||||
ios: 34,
|
||||
android: 36,
|
||||
}),
|
||||
width: 45,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
|
|
@ -9,9 +9,10 @@ import {Alert} from 'react-native';
|
|||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import {VALID_MIME_TYPES} from 'app/screens/edit_profile/edit_profile';
|
||||
import AttachmentButton from './attachment_button';
|
||||
import {PermissionTypes} from 'app/constants';
|
||||
|
||||
import AttachmentButton from './index';
|
||||
|
||||
jest.mock('react-intl');
|
||||
|
||||
jest.mock('Platform', () => {
|
||||
|
|
@ -23,9 +24,6 @@ jest.mock('Platform', () => {
|
|||
describe('AttachmentButton', () => {
|
||||
const formatMessage = jest.fn();
|
||||
const baseProps = {
|
||||
actions: {
|
||||
showModalOverCurrentContext: jest.fn(),
|
||||
},
|
||||
theme: Preferences.THEMES.default,
|
||||
blurTextBox: jest.fn(),
|
||||
maxFileSize: 10,
|
||||
|
|
@ -99,4 +97,4 @@ describe('AttachmentButton', () => {
|
|||
await wrapper.instance().hasPhotoPermission('camera');
|
||||
expect(Alert.alert).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -14,12 +14,12 @@ import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
|||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
export default class AutocompleteSelector extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
setAutocompleteSelector: PropTypes.func.isRequired,
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
label: PropTypes.string,
|
||||
placeholder: PropTypes.string.isRequired,
|
||||
|
|
@ -102,7 +102,7 @@ export default class AutocompleteSelector extends PureComponent {
|
|||
const title = placeholder || formatMessage({id: 'mobile.action_menu.select', defaultMessage: 'Select an option'});
|
||||
|
||||
actions.setAutocompleteSelector(dataSource, this.handleSelect, options);
|
||||
actions.goToScreen(screen, title);
|
||||
goToScreen(screen, title);
|
||||
});
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {getTeammateNameDisplaySetting, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
import {setAutocompleteSelector} from 'app/actions/views/post';
|
||||
|
||||
import AutocompleteSelector from './autocomplete_selector';
|
||||
|
|
@ -22,7 +21,6 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
setAutocompleteSelector,
|
||||
goToScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ import {
|
|||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import {getFullName} from 'mattermost-redux/utils/user_utils';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import BotTag from 'app/components/bot_tag';
|
||||
import GuestTag from 'app/components/guest_tag';
|
||||
|
|
@ -20,12 +21,10 @@ import {preventDoubleTap} from 'app/utils/tap';
|
|||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {isGuest} from 'app/utils/users';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
class ChannelIntro extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
creator: PropTypes.object,
|
||||
currentChannel: PropTypes.object.isRequired,
|
||||
currentChannelMembers: PropTypes.array.isRequired,
|
||||
|
|
@ -39,14 +38,14 @@ class ChannelIntro extends PureComponent {
|
|||
};
|
||||
|
||||
goToUserProfile = (userId) => {
|
||||
const {actions, intl} = this.props;
|
||||
const {intl} = this.props;
|
||||
const screen = 'UserProfile';
|
||||
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
|
||||
const passProps = {
|
||||
userId,
|
||||
};
|
||||
|
||||
actions.goToScreen(screen, title, passProps);
|
||||
goToScreen(screen, title, passProps);
|
||||
};
|
||||
|
||||
getDisplayName = (member) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
|
|
@ -11,7 +10,6 @@ import {getCurrentUserId, getUser, makeGetProfilesInChannel} from 'mattermost-re
|
|||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
import {getChannelMembersForDm} from 'app/selectors/channel';
|
||||
|
||||
import ChannelIntro from './channel_intro';
|
||||
|
|
@ -55,12 +53,4 @@ function makeMapStateToProps() {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(ChannelIntro);
|
||||
export default connect(makeMapStateToProps)(ChannelIntro);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {intlShape} from 'react-intl';
|
|||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {alertErrorWithFallback} from 'app/utils/general';
|
||||
import {popToRoot, dismissAllModals} from 'app/actions/navigation';
|
||||
|
||||
import {getChannelFromChannelName} from './channel_link_utils';
|
||||
|
||||
|
|
@ -23,10 +24,8 @@ export default class ChannelLink extends React.PureComponent {
|
|||
textStyle: CustomPropTypes.Style,
|
||||
channelsByName: PropTypes.object.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
dismissAllModals: PropTypes.func.isRequired,
|
||||
handleSelectChannel: PropTypes.func.isRequired,
|
||||
joinChannel: PropTypes.func.isRequired,
|
||||
popToRoot: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
|
|
@ -75,10 +74,11 @@ export default class ChannelLink extends React.PureComponent {
|
|||
}
|
||||
|
||||
if (channel.id) {
|
||||
const {dismissAllModals, handleSelectChannel, popToRoot} = this.props.actions;
|
||||
const {handleSelectChannel} = this.props.actions;
|
||||
handleSelectChannel(channel.id);
|
||||
dismissAllModals();
|
||||
popToRoot();
|
||||
|
||||
await dismissAllModals();
|
||||
await popToRoot();
|
||||
|
||||
if (this.props.onChannelLinkPress) {
|
||||
this.props.onChannelLinkPress(channel);
|
||||
|
|
|
|||
|
|
@ -11,9 +11,13 @@ import ChannelLink from './channel_link';
|
|||
|
||||
jest.mock('react-intl');
|
||||
|
||||
jest.mock('app/utils/general', () => ({
|
||||
alertErrorWithFallback: jest.fn(),
|
||||
}));
|
||||
jest.mock('app/utils/general', () => {
|
||||
const general = require.requireActual('app/utils/general');
|
||||
return {
|
||||
...general,
|
||||
alertErrorWithFallback: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('ChannelLink', () => {
|
||||
const formatMessage = jest.fn();
|
||||
|
|
@ -30,10 +34,8 @@ describe('ChannelLink', () => {
|
|||
textStyle: {color: '#3d3c40', fontSize: 15, lineHeight: 20},
|
||||
channelsByName,
|
||||
actions: {
|
||||
dismissAllModals: jest.fn(),
|
||||
handleSelectChannel: jest.fn(),
|
||||
joinChannel: jest.fn(),
|
||||
popToRoot: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -71,14 +73,14 @@ describe('ChannelLink', () => {
|
|||
expect(innerText.props().onPress).not.toBeDefined();
|
||||
});
|
||||
|
||||
test('should call props.actions and onChannelLinkPress on handlePress', () => {
|
||||
test('should call props.actions and onChannelLinkPress on handlePress', async () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelLink {...baseProps}/>,
|
||||
{context: {intl: {formatMessage}}},
|
||||
);
|
||||
|
||||
const channel = channelsByName.firstChannel;
|
||||
wrapper.instance().handlePress();
|
||||
await wrapper.instance().handlePress();
|
||||
expect(baseProps.actions.handleSelectChannel).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.actions.handleSelectChannel).toBeCalledWith(channel.id);
|
||||
expect(baseProps.onChannelLinkPress).toHaveBeenCalledTimes(1);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {getChannelsNameMapInCurrentTeam} from 'mattermost-redux/selectors/entiti
|
|||
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {dismissAllModals, popToRoot} from 'app/actions/navigation';
|
||||
import {handleSelectChannel} from 'app/actions/views/channel';
|
||||
|
||||
import ChannelLink from './channel_link';
|
||||
|
|
@ -44,10 +43,8 @@ function makeMapStateToProps() {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
dismissAllModals,
|
||||
handleSelectChannel,
|
||||
joinChannel,
|
||||
popToRoot,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import FormattedText from 'app/components/formatted_text';
|
|||
import {DeviceTypes} from 'app/constants';
|
||||
import {checkUpgradeType, isUpgradeAvailable} from 'app/utils/client_upgrade';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {showModal, dismissModal} from 'app/actions/navigation';
|
||||
|
||||
const {View: AnimatedView} = Animated;
|
||||
|
||||
|
|
@ -27,8 +28,6 @@ export default class ClientUpgradeListener extends PureComponent {
|
|||
actions: PropTypes.shape({
|
||||
logError: PropTypes.func.isRequired,
|
||||
setLastUpgradeCheck: PropTypes.func.isRequired,
|
||||
showModal: PropTypes.func.isRequired,
|
||||
dismissModal: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
currentVersion: PropTypes.string,
|
||||
downloadLink: PropTypes.string,
|
||||
|
|
@ -141,10 +140,9 @@ export default class ClientUpgradeListener extends PureComponent {
|
|||
};
|
||||
|
||||
handleLearnMore = () => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
|
||||
actions.dismissModal();
|
||||
dismissModal();
|
||||
|
||||
const screen = 'ClientUpgrade';
|
||||
const title = intl.formatMessage({id: 'mobile.client_upgrade', defaultMessage: 'Upgrade App'});
|
||||
|
|
@ -160,7 +158,7 @@ export default class ClientUpgradeListener extends PureComponent {
|
|||
},
|
||||
};
|
||||
|
||||
actions.showModal(screen, title, passProps, options);
|
||||
showModal(screen, title, passProps, options);
|
||||
|
||||
this.toggleUpgradeMessage(false);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {connect} from 'react-redux';
|
|||
import {logError} from 'mattermost-redux/actions/errors';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {showModal, dismissModal} from 'app/actions/navigation';
|
||||
import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade';
|
||||
import getClientUpgrade from 'app/selectors/client_upgrade';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
|
@ -33,8 +32,6 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
logError,
|
||||
setLastUpgradeCheck,
|
||||
showModal,
|
||||
dismissModal,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import FormattedText from 'app/components/formatted_text';
|
|||
import Loading from 'app/components/loading';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
|
||||
import {
|
||||
changeOpacity,
|
||||
|
|
@ -27,14 +28,10 @@ import {
|
|||
} from 'app/utils/theme';
|
||||
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {popTopScreen, dismissModal} from 'app/actions/navigation';
|
||||
|
||||
export default class EditChannelInfo extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
dismissModal: PropTypes.func.isRequired,
|
||||
popTopScreen: PropTypes.func.isRequired,
|
||||
}),
|
||||
theme: PropTypes.object.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
|
|
@ -97,11 +94,10 @@ export default class EditChannelInfo extends PureComponent {
|
|||
};
|
||||
|
||||
close = (goBack = false) => {
|
||||
const {actions} = this.props;
|
||||
if (goBack) {
|
||||
actions.popTopScreen();
|
||||
popTopScreen();
|
||||
} else {
|
||||
actions.dismissModal();
|
||||
dismissModal();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,6 @@ import EditChannelInfo from './edit_channel_info';
|
|||
|
||||
describe('EditChannelInfo', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
dismissModal: jest.fn(),
|
||||
popTopScreen: jest.fn(),
|
||||
},
|
||||
theme: Preferences.THEMES.default,
|
||||
deviceWidth: 400,
|
||||
deviceHeight: 600,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {
|
||||
dismissModal,
|
||||
popTopScreen,
|
||||
} from 'app/actions/navigation';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import EditChannelInfo from './edit_channel_info';
|
||||
|
||||
|
|
@ -17,13 +12,4 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
dismissModal,
|
||||
popTopScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EditChannelInfo);
|
||||
export default connect(mapStateToProps)(EditChannelInfo);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
|||
import {DeviceTypes} from 'app/constants/';
|
||||
import mattermostBucket from 'app/mattermost_bucket';
|
||||
import {changeOpacity} from 'app/utils/theme';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
const {DOCUMENTS_PATH} = DeviceTypes;
|
||||
const DOWNLOADING_OFFSET = 28;
|
||||
|
|
@ -37,9 +38,6 @@ const circularProgressWidth = 4;
|
|||
|
||||
export default class FileAttachmentDocument extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
backgroundColor: PropTypes.string,
|
||||
canDownloadFiles: PropTypes.bool.isRequired,
|
||||
iconHeight: PropTypes.number,
|
||||
|
|
@ -193,13 +191,14 @@ export default class FileAttachmentDocument extends PureComponent {
|
|||
};
|
||||
|
||||
previewTextFile = (file, delay = 2000) => {
|
||||
const {actions} = this.props;
|
||||
const {data} = file;
|
||||
const prefix = Platform.OS === 'android' ? 'file:/' : '';
|
||||
const path = `${DOCUMENTS_PATH}/${data.id}-${file.caption}`;
|
||||
const readFile = RNFetchBlob.fs.readFile(`${prefix}${path}`, 'utf8');
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
this.setState({downloading: false, progress: 0});
|
||||
|
||||
const content = await readFile;
|
||||
const screen = 'TextPreview';
|
||||
const title = file.caption;
|
||||
|
|
@ -207,8 +206,7 @@ export default class FileAttachmentDocument extends PureComponent {
|
|||
content,
|
||||
};
|
||||
|
||||
actions.goToScreen(screen, title, passProps);
|
||||
this.setState({downloading: false, progress: 0});
|
||||
goToScreen(screen, title, passProps);
|
||||
} catch (error) {
|
||||
RNFetchBlob.fs.unlink(path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,8 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
import FileAttachmentDocument from './file_attachment_document';
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps, null, {forwardRef: true})(FileAttachmentDocument);
|
||||
export default connect(null, null, null, {forwardRef: true})(FileAttachmentDocument);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ export default class FileAttachmentList extends Component {
|
|||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
loadFilesForPostIfNecessary: PropTypes.func.isRequired,
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
canDownloadFiles: PropTypes.bool.isRequired,
|
||||
fileIds: PropTypes.array.isRequired,
|
||||
|
|
@ -122,8 +121,7 @@ export default class FileAttachmentList extends Component {
|
|||
};
|
||||
|
||||
handlePreviewPress = preventDoubleTap((idx) => {
|
||||
const {actions} = this.props;
|
||||
previewImageAtIndex(this.items, idx, this.galleryFiles, actions.showModalOverCurrentContext);
|
||||
previewImageAtIndex(this.items, idx, this.galleryFiles);
|
||||
});
|
||||
|
||||
renderItems = () => {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ describe('PostAttachmentOpenGraph', () => {
|
|||
const baseProps = {
|
||||
actions: {
|
||||
loadFilesForPostIfNecessary,
|
||||
showModalOverCurrentContext: jest.fn(),
|
||||
},
|
||||
canDownloadFiles: true,
|
||||
deviceHeight: 680,
|
||||
|
|
@ -72,7 +71,6 @@ describe('PostAttachmentOpenGraph', () => {
|
|||
files: [],
|
||||
actions: {
|
||||
loadFilesForPostIfNecessary: loadFilesForPostIfNecessaryMock,
|
||||
showModalOverCurrentContext: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {canDownloadFilesOnMobile} from 'mattermost-redux/selectors/entities/gene
|
|||
import {makeGetFilesForPost} from 'mattermost-redux/selectors/entities/files';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
import {loadFilesForPostIfNecessary} from 'app/actions/views/channel';
|
||||
|
||||
import FileAttachmentList from './file_attachment_list';
|
||||
|
|
@ -28,7 +27,6 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
loadFilesForPostIfNecessary,
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {submitInteractiveDialog} from 'mattermost-redux/actions/integrations';
|
||||
|
||||
import {showModal} from 'app/actions/navigation';
|
||||
|
||||
import InteractiveDialogController from './interactive_dialog_controller';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
@ -20,7 +18,6 @@ function mapStateToProps(state) {
|
|||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
showModal,
|
||||
submitInteractiveDialog,
|
||||
}, dispatch),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
|||
import {Alert} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {showModal} from 'app/actions/navigation';
|
||||
|
||||
export default class InteractiveDialogController extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
showModal: PropTypes.func.isRequired,
|
||||
submitInteractiveDialog: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
triggerId: PropTypes.string,
|
||||
|
|
@ -88,7 +89,7 @@ export default class InteractiveDialogController extends PureComponent {
|
|||
},
|
||||
};
|
||||
|
||||
this.props.actions.showModal('InteractiveDialog', dialog.title, null, options);
|
||||
showModal('InteractiveDialog', dialog.title, null, options);
|
||||
}
|
||||
|
||||
handleCancel = (dialog, url) => {
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ function getBaseProps(triggerId, elements, introductionText) {
|
|||
|
||||
return {
|
||||
actions: {
|
||||
showModal: jest.fn(),
|
||||
submitInteractiveDialog: jest.fn(),
|
||||
},
|
||||
triggerId,
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
|
||||
export default class Hashtag extends React.PureComponent {
|
||||
static propTypes = {
|
||||
hashtag: PropTypes.string.isRequired,
|
||||
linkStyle: CustomPropTypes.Style.isRequired,
|
||||
onHashtagPress: PropTypes.func,
|
||||
actions: PropTypes.shape({
|
||||
popToRoot: PropTypes.func.isRequired,
|
||||
showSearchModal: PropTypes.func.isRequired,
|
||||
dismissAllModals: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
handlePress = () => {
|
||||
const {
|
||||
onHashtagPress,
|
||||
hashtag,
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
if (onHashtagPress) {
|
||||
onHashtagPress(hashtag);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Close thread view, permalink view, etc
|
||||
actions.dismissAllModals();
|
||||
actions.popToRoot();
|
||||
|
||||
actions.showSearchModal('#' + this.props.hashtag);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Text
|
||||
style={this.props.linkStyle}
|
||||
onPress={this.handlePress}
|
||||
>
|
||||
{`#${this.props.hashtag}`}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import Hashtag from './hashtag';
|
||||
|
||||
describe('Hashtag', () => {
|
||||
const baseProps = {
|
||||
hashtag: 'test',
|
||||
linkStyle: {color: 'red'},
|
||||
actions: {
|
||||
showSearchModal: jest.fn(),
|
||||
dismissAllModals: jest.fn(),
|
||||
popToRoot: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(<Hashtag {...baseProps}/>);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should open hashtag search on click', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<Hashtag {...props}/>);
|
||||
|
||||
wrapper.find(Text).simulate('press');
|
||||
|
||||
expect(props.actions.dismissAllModals).toHaveBeenCalled();
|
||||
expect(props.actions.popToRoot).toHaveBeenCalled();
|
||||
expect(props.actions.showSearchModal).toHaveBeenCalledWith('#test');
|
||||
});
|
||||
|
||||
test('should call onHashtagPress if provided', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
onHashtagPress: jest.fn(),
|
||||
};
|
||||
|
||||
const wrapper = shallow(<Hashtag {...props}/>);
|
||||
|
||||
wrapper.find(Text).simulate('press');
|
||||
|
||||
expect(props.actions.dismissAllModals).not.toBeCalled();
|
||||
expect(props.actions.popToRoot).not.toBeCalled();
|
||||
expect(props.actions.showSearchModal).not.toBeCalled();
|
||||
|
||||
expect(props.onHashtagPress).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,25 +1,47 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import {
|
||||
popToRoot,
|
||||
showSearchModal,
|
||||
dismissAllModals,
|
||||
} from 'app/actions/navigation';
|
||||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {popToRoot, showSearchModal, dismissAllModals} from 'app/actions/navigation';
|
||||
|
||||
import Hashtag from './hashtag';
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
popToRoot,
|
||||
showSearchModal,
|
||||
dismissAllModals,
|
||||
}, dispatch),
|
||||
export default class Hashtag extends React.PureComponent {
|
||||
static propTypes = {
|
||||
hashtag: PropTypes.string.isRequired,
|
||||
linkStyle: CustomPropTypes.Style.isRequired,
|
||||
onHashtagPress: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(Hashtag);
|
||||
handlePress = async () => {
|
||||
const {
|
||||
onHashtagPress,
|
||||
hashtag,
|
||||
} = this.props;
|
||||
|
||||
if (onHashtagPress) {
|
||||
onHashtagPress(hashtag);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Close thread view, permalink view, etc
|
||||
await dismissAllModals();
|
||||
await popToRoot();
|
||||
|
||||
showSearchModal('#' + this.props.hashtag);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Text
|
||||
style={this.props.linkStyle}
|
||||
onPress={this.handlePress}
|
||||
>
|
||||
{`#${this.props.hashtag}`}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
app/components/markdown/hashtag/index.test.js
Normal file
61
app/components/markdown/hashtag/index.test.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import * as NavigationActions from 'app/actions/navigation';
|
||||
|
||||
import Hashtag from './index';
|
||||
|
||||
describe('Hashtag', () => {
|
||||
const baseProps = {
|
||||
hashtag: 'test',
|
||||
linkStyle: {color: 'red'},
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(<Hashtag {...baseProps}/>);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('handlePress should open hashtag search', async () => {
|
||||
const dismissAllModals = jest.spyOn(NavigationActions, 'dismissAllModals');
|
||||
const popToRoot = jest.spyOn(NavigationActions, 'popToRoot');
|
||||
const showSearchModal = jest.spyOn(NavigationActions, 'showSearchModal');
|
||||
|
||||
const props = {
|
||||
...baseProps,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<Hashtag {...props}/>);
|
||||
|
||||
await wrapper.instance().handlePress();
|
||||
|
||||
expect(dismissAllModals).toHaveBeenCalled();
|
||||
expect(popToRoot).toHaveBeenCalled();
|
||||
expect(showSearchModal).toHaveBeenCalledWith('#test');
|
||||
});
|
||||
|
||||
test('handlePress should call onHashtagPress if provided', async () => {
|
||||
const dismissAllModals = jest.spyOn(NavigationActions, 'dismissAllModals');
|
||||
const popToRoot = jest.spyOn(NavigationActions, 'popToRoot');
|
||||
const showSearchModal = jest.spyOn(NavigationActions, 'showSearchModal');
|
||||
|
||||
const props = {
|
||||
...baseProps,
|
||||
onHashtagPress: jest.fn(),
|
||||
};
|
||||
|
||||
const wrapper = shallow(<Hashtag {...props}/>);
|
||||
|
||||
await wrapper.instance().handlePress();
|
||||
|
||||
expect(dismissAllModals).not.toBeCalled();
|
||||
expect(popToRoot).not.toBeCalled();
|
||||
expect(showSearchModal).not.toBeCalled();
|
||||
|
||||
expect(props.onHashtagPress).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,13 +1,10 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
import MarkdownCodeBlock from './markdown_code_block';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
@ -16,12 +13,4 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MarkdownCodeBlock);
|
||||
export default connect(mapStateToProps)(MarkdownCodeBlock);
|
||||
|
|
|
|||
|
|
@ -19,14 +19,12 @@ import {getDisplayNameForLanguage} from 'app/utils/markdown';
|
|||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
const MAX_LINES = 4;
|
||||
|
||||
export default class MarkdownCodeBlock extends React.PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
language: PropTypes.string,
|
||||
content: PropTypes.string.isRequired,
|
||||
|
|
@ -42,7 +40,7 @@ export default class MarkdownCodeBlock extends React.PureComponent {
|
|||
};
|
||||
|
||||
handlePress = preventDoubleTap(() => {
|
||||
const {actions, language, content} = this.props;
|
||||
const {language, content} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'Code';
|
||||
const passProps = {
|
||||
|
|
@ -68,7 +66,7 @@ export default class MarkdownCodeBlock extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
actions.goToScreen(screen, title, passProps);
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
|
||||
handleLongPress = async () => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
|
||||
|
||||
import {showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
|
||||
import MarkdownImage from './markdown_image';
|
||||
|
|
@ -19,12 +16,4 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MarkdownImage);
|
||||
export default connect(mapStateToProps)(MarkdownImage);
|
||||
|
|
|
|||
|
|
@ -34,9 +34,6 @@ const VIEWPORT_IMAGE_REPLY_OFFSET = 13;
|
|||
|
||||
export default class MarkdownImage extends React.Component {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
children: PropTypes.node,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
|
|
@ -177,7 +174,6 @@ export default class MarkdownImage extends React.Component {
|
|||
originalWidth,
|
||||
uri,
|
||||
} = this.state;
|
||||
const {actions} = this.props;
|
||||
const link = this.getSource();
|
||||
let filename = link.substring(link.lastIndexOf('/') + 1, link.indexOf('?') === -1 ? link.length : link.indexOf('?'));
|
||||
const extension = filename.split('.').pop();
|
||||
|
|
@ -199,7 +195,7 @@ export default class MarkdownImage extends React.Component {
|
|||
},
|
||||
}];
|
||||
|
||||
previewImageAtIndex([this.refs.item], 0, files, actions.showModalOverCurrentContext);
|
||||
previewImageAtIndex([this.refs.item], 0, files);
|
||||
};
|
||||
|
||||
loadImageSize = (source) => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
import MarkdownTable from './markdown_table';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
@ -16,12 +13,4 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MarkdownTable);
|
||||
export default connect(mapStateToProps)(MarkdownTable);
|
||||
|
|
|
|||
|
|
@ -11,17 +11,16 @@ import {
|
|||
import LinearGradient from 'react-native-linear-gradient';
|
||||
|
||||
import {CELL_WIDTH} from 'app/components/markdown/markdown_table_cell/markdown_table_cell';
|
||||
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
const MAX_HEIGHT = 300;
|
||||
|
||||
export default class MarkdownTable extends React.PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
numColumns: PropTypes.number.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
|
|
@ -46,7 +45,6 @@ export default class MarkdownTable extends React.PureComponent {
|
|||
};
|
||||
|
||||
handlePress = preventDoubleTap(() => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'Table';
|
||||
const title = intl.formatMessage({
|
||||
|
|
@ -58,7 +56,7 @@ export default class MarkdownTable extends React.PureComponent {
|
|||
tableWidth: this.getTableWidth(),
|
||||
};
|
||||
|
||||
actions.goToScreen(screen, title, passProps);
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
|
||||
handleContainerLayout = (e) => {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
import MarkdownTableImage from './markdown_table_image';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
@ -18,12 +15,4 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MarkdownTableImage);
|
||||
export default connect(mapStateToProps)(MarkdownTableImage);
|
||||
|
|
|
|||
|
|
@ -9,12 +9,10 @@ import {Text} from 'react-native';
|
|||
import CustomPropTypes from 'app/constants/custom_prop_types';
|
||||
import {getCurrentServerUrl} from 'app/init/credentials';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
export default class MarkdownTableImage extends React.PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
source: PropTypes.string.isRequired,
|
||||
textStyle: CustomPropTypes.Style.isRequired,
|
||||
|
|
@ -27,7 +25,6 @@ export default class MarkdownTableImage extends React.PureComponent {
|
|||
};
|
||||
|
||||
handlePress = preventDoubleTap(() => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'TableImage';
|
||||
const title = intl.formatMessage({
|
||||
|
|
@ -38,7 +35,7 @@ export default class MarkdownTableImage extends React.PureComponent {
|
|||
imageSource: this.getImageSource(),
|
||||
};
|
||||
|
||||
actions.goToScreen(screen, title, passProps);
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
|
||||
getImageSource = async () => {
|
||||
|
|
|
|||
|
|
@ -258,8 +258,6 @@ function getLastSibling(node) {
|
|||
export function highlightMentions(ast, mentionKeys) {
|
||||
const walker = ast.walker();
|
||||
|
||||
// console.warn(mentionKeys);
|
||||
|
||||
let e;
|
||||
while ((e = walker.next())) {
|
||||
if (!e.entering) {
|
||||
|
|
|
|||
|
|
@ -2966,28 +2966,28 @@ describe('Components.Markdown.transform', () => {
|
|||
// Confirms that all parent, child, and sibling linkages are correct and go both ways.
|
||||
function verifyAst(node) {
|
||||
if (node.prev && node.prev.next !== node) {
|
||||
console.error('node is not linked properly to prev');
|
||||
console.error('node is not linked properly to prev'); //eslint-disable-line no-console
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.next && node.next.prev !== node) {
|
||||
console.error('node is not linked properly to next');
|
||||
console.error('node is not linked properly to next'); //eslint-disable-line no-console
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!node.firstChild && node.lastChild) {
|
||||
console.error('node has children, but is not linked to first child');
|
||||
console.error('node has children, but is not linked to first child'); //eslint-disable-line no-console
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.firstChild && !node.lastChild) {
|
||||
console.error('node has children, but is not linked to last child');
|
||||
console.error('node has children, but is not linked to last child'); //eslint-disable-line no-console
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let child = node.firstChild; child; child = child.next) {
|
||||
if (child.parent !== node) {
|
||||
console.error('node is not linked properly to child');
|
||||
console.error('node is not linked properly to child'); //eslint-disable-line no-console
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -2996,18 +2996,18 @@ function verifyAst(node) {
|
|||
}
|
||||
|
||||
if (!child.next && child !== node.lastChild) {
|
||||
console.error('node children are not linked correctly');
|
||||
console.error('node children are not linked correctly'); //eslint-disable-line no-console
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.firstChild && node.firstChild.prev) {
|
||||
console.error('node\'s first child has previous sibling');
|
||||
console.error('node\'s first child has previous sibling'); //eslint-disable-line no-console
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.lastChild && node.lastChild.next) {
|
||||
console.error('node\'s last child has next sibling');
|
||||
console.error('node\'s last child has next sibling'); //eslint-disable-line no-console
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,178 +0,0 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Image, View} from 'react-native';
|
||||
|
||||
import ProgressiveImage from 'app/components/progressive_image';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {isGifTooLarge, previewImageAtIndex, calculateDimensions} from 'app/utils/images';
|
||||
import ImageCacheManager from 'app/utils/image_cache_manager';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const VIEWPORT_IMAGE_OFFSET = 100;
|
||||
const VIEWPORT_IMAGE_CONTAINER_OFFSET = 10;
|
||||
|
||||
export default class AttachmentImage extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
imageMetadata: PropTypes.object,
|
||||
imageUrl: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hasImage: Boolean(props.imageUrl),
|
||||
imageUri: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
const {imageUrl, imageMetadata} = this.props;
|
||||
|
||||
this.setViewPortMaxWidth();
|
||||
if (imageMetadata) {
|
||||
this.setImageDimensionsFromMeta(null, imageMetadata);
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
ImageCacheManager.cache(null, imageUrl, this.setImageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.imageUrl && (prevProps.imageUrl !== this.props.imageUrl)) {
|
||||
ImageCacheManager.cache(null, this.props.imageUrl, this.setImageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
handlePreviewImage = () => {
|
||||
const {actions, imageUrl} = this.props;
|
||||
const {
|
||||
imageUri: uri,
|
||||
originalHeight,
|
||||
originalWidth,
|
||||
} = this.state;
|
||||
|
||||
let filename = imageUrl.substring(imageUrl.lastIndexOf('/') + 1, imageUrl.indexOf('?') === -1 ? imageUrl.length : imageUrl.indexOf('?'));
|
||||
const extension = filename.split('.').pop();
|
||||
|
||||
if (extension === filename) {
|
||||
const ext = filename.indexOf('.') === -1 ? '.png' : filename.substring(filename.lastIndexOf('.'));
|
||||
filename = `${filename}${ext}`;
|
||||
}
|
||||
|
||||
const files = [{
|
||||
caption: filename,
|
||||
dimensions: {
|
||||
height: originalHeight,
|
||||
width: originalWidth,
|
||||
},
|
||||
source: {uri},
|
||||
data: {
|
||||
localPath: uri,
|
||||
},
|
||||
}];
|
||||
previewImageAtIndex([this.refs.item], 0, files, actions.showModalOverCurrentContext);
|
||||
};
|
||||
|
||||
setImageDimensions = (imageUri, dimensions, originalWidth, originalHeight) => {
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
...dimensions,
|
||||
originalWidth,
|
||||
originalHeight,
|
||||
imageUri,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setImageDimensionsFromMeta = (imageUri, imageMetadata) => {
|
||||
const dimensions = calculateDimensions(imageMetadata.height, imageMetadata.width, this.maxImageWidth);
|
||||
this.setImageDimensions(imageUri, dimensions, imageMetadata.width, imageMetadata.height);
|
||||
};
|
||||
|
||||
setImageUrl = (imageURL) => {
|
||||
const {imageMetadata} = this.props;
|
||||
|
||||
if (imageMetadata) {
|
||||
this.setImageDimensionsFromMeta(imageURL, imageMetadata);
|
||||
return;
|
||||
}
|
||||
|
||||
Image.getSize(imageURL, (width, height) => {
|
||||
const dimensions = calculateDimensions(height, width, this.maxImageWidth);
|
||||
this.setImageDimensions(imageURL, dimensions, width, height);
|
||||
}, () => null);
|
||||
};
|
||||
|
||||
setViewPortMaxWidth = () => {
|
||||
const {deviceWidth, deviceHeight} = this.props;
|
||||
const viewPortWidth = deviceWidth > deviceHeight ? deviceHeight : deviceWidth;
|
||||
this.maxImageWidth = viewPortWidth - VIEWPORT_IMAGE_OFFSET;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {imageMetadata, theme} = this.props;
|
||||
const {hasImage, height, imageUri, width} = this.state;
|
||||
|
||||
if (!hasImage || isGifTooLarge(imageMetadata)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let progressiveImage;
|
||||
if (imageUri) {
|
||||
progressiveImage = (
|
||||
<ProgressiveImage
|
||||
ref='image'
|
||||
style={{height, width}}
|
||||
imageUri={imageUri}
|
||||
resizeMode='contain'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
progressiveImage = (<View style={{width, height}}/>);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.handlePreviewImage}
|
||||
style={[style.container, {width: this.maxImageWidth + VIEWPORT_IMAGE_CONTAINER_OFFSET}]}
|
||||
type={'none'}
|
||||
>
|
||||
<View
|
||||
ref='item'
|
||||
style={[style.imageContainer, {width, height}]}
|
||||
>
|
||||
{progressiveImage}
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
marginTop: 5,
|
||||
},
|
||||
imageContainer: {
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderWidth: 1,
|
||||
borderRadius: 2,
|
||||
flex: 1,
|
||||
padding: 5,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -1,19 +1,175 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Image, View} from 'react-native';
|
||||
|
||||
import {showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
import ProgressiveImage from 'app/components/progressive_image';
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {isGifTooLarge, previewImageAtIndex, calculateDimensions} from 'app/utils/images';
|
||||
import ImageCacheManager from 'app/utils/image_cache_manager';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
import AttachmentImage from './attachment_image';
|
||||
const VIEWPORT_IMAGE_OFFSET = 100;
|
||||
const VIEWPORT_IMAGE_CONTAINER_OFFSET = 10;
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
export default class AttachmentImage extends PureComponent {
|
||||
static propTypes = {
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
imageMetadata: PropTypes.object,
|
||||
imageUrl: PropTypes.string,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hasImage: Boolean(props.imageUrl),
|
||||
imageUri: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
const {imageUrl, imageMetadata} = this.props;
|
||||
|
||||
this.setViewPortMaxWidth();
|
||||
if (imageMetadata) {
|
||||
this.setImageDimensionsFromMeta(null, imageMetadata);
|
||||
}
|
||||
|
||||
if (imageUrl) {
|
||||
ImageCacheManager.cache(null, imageUrl, this.setImageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.imageUrl && (prevProps.imageUrl !== this.props.imageUrl)) {
|
||||
ImageCacheManager.cache(null, this.props.imageUrl, this.setImageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
handlePreviewImage = () => {
|
||||
const {imageUrl} = this.props;
|
||||
const {
|
||||
imageUri: uri,
|
||||
originalHeight,
|
||||
originalWidth,
|
||||
} = this.state;
|
||||
|
||||
let filename = imageUrl.substring(imageUrl.lastIndexOf('/') + 1, imageUrl.indexOf('?') === -1 ? imageUrl.length : imageUrl.indexOf('?'));
|
||||
const extension = filename.split('.').pop();
|
||||
|
||||
if (extension === filename) {
|
||||
const ext = filename.indexOf('.') === -1 ? '.png' : filename.substring(filename.lastIndexOf('.'));
|
||||
filename = `${filename}${ext}`;
|
||||
}
|
||||
|
||||
const files = [{
|
||||
caption: filename,
|
||||
dimensions: {
|
||||
height: originalHeight,
|
||||
width: originalWidth,
|
||||
},
|
||||
source: {uri},
|
||||
data: {
|
||||
localPath: uri,
|
||||
},
|
||||
}];
|
||||
previewImageAtIndex([this.refs.item], 0, files);
|
||||
};
|
||||
|
||||
setImageDimensions = (imageUri, dimensions, originalWidth, originalHeight) => {
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
...dimensions,
|
||||
originalWidth,
|
||||
originalHeight,
|
||||
imageUri,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setImageDimensionsFromMeta = (imageUri, imageMetadata) => {
|
||||
const dimensions = calculateDimensions(imageMetadata.height, imageMetadata.width, this.maxImageWidth);
|
||||
this.setImageDimensions(imageUri, dimensions, imageMetadata.width, imageMetadata.height);
|
||||
};
|
||||
|
||||
setImageUrl = (imageURL) => {
|
||||
const {imageMetadata} = this.props;
|
||||
|
||||
if (imageMetadata) {
|
||||
this.setImageDimensionsFromMeta(imageURL, imageMetadata);
|
||||
return;
|
||||
}
|
||||
|
||||
Image.getSize(imageURL, (width, height) => {
|
||||
const dimensions = calculateDimensions(height, width, this.maxImageWidth);
|
||||
this.setImageDimensions(imageURL, dimensions, width, height);
|
||||
}, () => null);
|
||||
};
|
||||
|
||||
setViewPortMaxWidth = () => {
|
||||
const {deviceWidth, deviceHeight} = this.props;
|
||||
const viewPortWidth = deviceWidth > deviceHeight ? deviceHeight : deviceWidth;
|
||||
this.maxImageWidth = viewPortWidth - VIEWPORT_IMAGE_OFFSET;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {imageMetadata, theme} = this.props;
|
||||
const {hasImage, height, imageUri, width} = this.state;
|
||||
|
||||
if (!hasImage || isGifTooLarge(imageMetadata)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
let progressiveImage;
|
||||
if (imageUri) {
|
||||
progressiveImage = (
|
||||
<ProgressiveImage
|
||||
ref='image'
|
||||
style={{height, width}}
|
||||
imageUri={imageUri}
|
||||
resizeMode='contain'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
progressiveImage = (<View style={{width, height}}/>);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.handlePreviewImage}
|
||||
style={[style.container, {width: this.maxImageWidth + VIEWPORT_IMAGE_CONTAINER_OFFSET}]}
|
||||
type={'none'}
|
||||
>
|
||||
<View
|
||||
ref='item'
|
||||
style={[style.imageContainer, {width, height}]}
|
||||
>
|
||||
{progressiveImage}
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(AttachmentImage);
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
marginTop: 5,
|
||||
},
|
||||
imageContainer: {
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderWidth: 1,
|
||||
borderRadius: 2,
|
||||
flex: 1,
|
||||
padding: 5,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,13 +11,10 @@ const originalGetSizeFn = Image.getSize;
|
|||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import AttachmentImage from './attachment_image';
|
||||
import AttachmentImage from './index';
|
||||
|
||||
describe('AttachmentImage', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
showModalOverCurrentContext: jest.fn(),
|
||||
},
|
||||
deviceHeight: 256,
|
||||
deviceWidth: 128,
|
||||
imageMetadata: {width: 32, height: 32},
|
||||
|
|
@ -12,7 +12,6 @@ import {getUser, getCurrentUserId} from 'mattermost-redux/selectors/entities/use
|
|||
import {getMyPreferences, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {isPostFlagged, isSystemMessage} from 'mattermost-redux/utils/post_utils';
|
||||
|
||||
import {goToScreen, showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
import {insertToDraft, setPostTooltipVisible} from 'app/actions/views/channel';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
|
|
@ -96,8 +95,6 @@ function mapDispatchToProps(dispatch) {
|
|||
removePost,
|
||||
setPostTooltipVisible,
|
||||
insertToDraft,
|
||||
goToScreen,
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ import {
|
|||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import {isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils';
|
||||
|
||||
import PostBody from 'app/components/post_body';
|
||||
import PostHeader from 'app/components/post_header';
|
||||
import PostPreHeader from 'app/components/post_header/post_pre_header';
|
||||
|
|
@ -22,10 +26,7 @@ import {preventDoubleTap} from 'app/utils/tap';
|
|||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import {isPostEphemeral, isPostPendingOrFailed, isSystemMessage} from 'mattermost-redux/utils/post_utils';
|
||||
import {goToScreen, showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
|
||||
import Config from 'assets/config';
|
||||
|
||||
|
|
@ -35,8 +36,6 @@ export default class Post extends PureComponent {
|
|||
createPost: PropTypes.func.isRequired,
|
||||
insertToDraft: PropTypes.func.isRequired,
|
||||
removePost: PropTypes.func.isRequired,
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
channelIsReadOnly: PropTypes.bool,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
|
|
@ -91,7 +90,7 @@ export default class Post extends PureComponent {
|
|||
|
||||
goToUserProfile = () => {
|
||||
const {intl} = this.context;
|
||||
const {actions, post} = this.props;
|
||||
const {post} = this.props;
|
||||
const screen = 'UserProfile';
|
||||
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
|
||||
const passProps = {
|
||||
|
|
@ -100,7 +99,7 @@ export default class Post extends PureComponent {
|
|||
|
||||
Keyboard.dismiss();
|
||||
requestAnimationFrame(() => {
|
||||
actions.goToScreen(screen, title, passProps);
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -141,7 +140,7 @@ export default class Post extends PureComponent {
|
|||
}],
|
||||
};
|
||||
|
||||
this.props.actions.showModalOverCurrentContext(screen, passProps);
|
||||
showModalOverCurrentContext(screen, passProps);
|
||||
};
|
||||
|
||||
handlePress = preventDoubleTap(() => {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ import {bindActionCreators} from 'redux';
|
|||
|
||||
import {getOpenGraphMetadata} from 'mattermost-redux/actions/posts';
|
||||
|
||||
import {showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
|
||||
import PostAttachmentOpenGraph from './post_attachment_opengraph';
|
||||
|
|
@ -22,7 +20,6 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
getOpenGraphMetadata,
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
getOpenGraphMetadata: PropTypes.func.isRequired,
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
deviceWidth: PropTypes.number.isRequired,
|
||||
|
|
@ -194,7 +193,6 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
originalWidth,
|
||||
originalHeight,
|
||||
} = this.state;
|
||||
const {actions} = this.props;
|
||||
const filename = this.getFilename(link);
|
||||
|
||||
const files = [{
|
||||
|
|
@ -209,7 +207,7 @@ export default class PostAttachmentOpenGraph extends PureComponent {
|
|||
},
|
||||
}];
|
||||
|
||||
previewImageAtIndex([this.refs.item], 0, files, actions.showModalOverCurrentContext);
|
||||
previewImageAtIndex([this.refs.item], 0, files);
|
||||
};
|
||||
|
||||
renderDescription = () => {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ describe('PostAttachmentOpenGraph', () => {
|
|||
const baseProps = {
|
||||
actions: {
|
||||
getOpenGraphMetadata: jest.fn(),
|
||||
showModalOverCurrentContext: jest.fn(),
|
||||
},
|
||||
deviceHeight: 600,
|
||||
deviceWidth: 400,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {General, Posts} from 'mattermost-redux/constants';
|
||||
|
|
@ -22,8 +21,6 @@ import {
|
|||
} from 'mattermost-redux/utils/post_utils';
|
||||
import {isAdmin as checkIsAdmin, isSystemAdmin as checkIsSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import {showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
|
||||
import {hasEmojisOnly} from 'app/utils/emoji_utils';
|
||||
|
|
@ -106,12 +103,4 @@ export function makeMapStateToProps() {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps, null, {forwardRef: true})(PostBody);
|
||||
export default connect(makeMapStateToProps, null, null, {forwardRef: true})(PostBody);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {emptyFunction} from 'app/utils/general';
|
|||
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
|
||||
import telemetry from 'app/telemetry';
|
||||
|
||||
|
|
@ -34,9 +35,6 @@ const SHOW_MORE_HEIGHT = 60;
|
|||
|
||||
export default class PostBody extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
canDelete: PropTypes.bool,
|
||||
channelIsReadOnly: PropTypes.bool.isRequired,
|
||||
deviceHeight: PropTypes.number.isRequired,
|
||||
|
|
@ -133,7 +131,6 @@ export default class PostBody extends PureComponent {
|
|||
onHashtagPress,
|
||||
onPermalinkPress,
|
||||
post,
|
||||
actions,
|
||||
} = this.props;
|
||||
const screen = 'LongPost';
|
||||
const passProps = {
|
||||
|
|
@ -148,7 +145,7 @@ export default class PostBody extends PureComponent {
|
|||
},
|
||||
};
|
||||
|
||||
actions.showModalOverCurrentContext(screen, passProps, options);
|
||||
showModalOverCurrentContext(screen, passProps, options);
|
||||
});
|
||||
|
||||
showPostOptions = () => {
|
||||
|
|
@ -165,7 +162,6 @@ export default class PostBody extends PureComponent {
|
|||
post,
|
||||
showAddReaction,
|
||||
location,
|
||||
actions,
|
||||
} = this.props;
|
||||
|
||||
if (isSystemMessage && (!canDelete || hasBeenDeleted)) {
|
||||
|
|
@ -191,7 +187,7 @@ export default class PostBody extends PureComponent {
|
|||
|
||||
Keyboard.dismiss();
|
||||
requestAnimationFrame(() => {
|
||||
actions.showModalOverCurrentContext(screen, passProps);
|
||||
showModalOverCurrentContext(screen, passProps);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,6 @@ import PostBody from './post_body.js';
|
|||
|
||||
describe('PostBody', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
showModalOverCurrentContext: jest.fn(),
|
||||
},
|
||||
canDelete: true,
|
||||
channelIsReadOnly: false,
|
||||
deviceHeight: 1920,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
|||
import {getOpenGraphMetadataForUrl} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getBool, getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {getDimensions} from 'app/selectors/device';
|
||||
import {extractFirstLink} from 'app/utils/url';
|
||||
|
|
@ -76,7 +75,6 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
getRedirectLocation,
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
getRedirectLocation: PropTypes.func.isRequired,
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
baseTextStyle: CustomPropTypes.Style,
|
||||
blockStyles: PropTypes.object,
|
||||
|
|
@ -412,7 +411,6 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
handlePreviewImage = (imageRef) => {
|
||||
const {shortenedLink} = this.state;
|
||||
let {link} = this.props;
|
||||
const {actions} = this.props;
|
||||
if (shortenedLink) {
|
||||
link = shortenedLink;
|
||||
}
|
||||
|
|
@ -434,7 +432,7 @@ export default class PostBodyAdditionalContent extends PureComponent {
|
|||
},
|
||||
}];
|
||||
|
||||
previewImageAtIndex([imageRef], 0, files, actions.showModalOverCurrentContext);
|
||||
previewImageAtIndex([imageRef], 0, files);
|
||||
};
|
||||
|
||||
playYouTubeVideo = () => {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {getConfig, getCurrentUrl} from 'mattermost-redux/selectors/entities/gene
|
|||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {makePreparePostIdsForPostList, START_OF_NEW_MESSAGES} from 'mattermost-redux/utils/post_list';
|
||||
|
||||
import {showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
import {handleSelectChannelByName, loadChannelsByTeamName, refreshChannelWithRetry} from 'app/actions/views/channel';
|
||||
import {setDeepLinkURL} from 'app/actions/views/root';
|
||||
|
||||
|
|
@ -43,7 +42,6 @@ function mapDispatchToProps(dispatch) {
|
|||
refreshChannelWithRetry,
|
||||
selectFocusedPostId,
|
||||
setDeepLinkURL,
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {makeExtraData} from 'app/utils/list_view';
|
|||
import {changeOpacity} from 'app/utils/theme';
|
||||
import {matchDeepLink} from 'app/utils/url';
|
||||
import telemetry from 'app/telemetry';
|
||||
import {showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
|
||||
import DateHeader from './date_header';
|
||||
import NewMessagesDivider from './new_messages_divider';
|
||||
|
|
@ -41,7 +42,6 @@ export default class PostList extends PureComponent {
|
|||
refreshChannelWithRetry: PropTypes.func.isRequired,
|
||||
selectFocusedPostId: PropTypes.func.isRequired,
|
||||
setDeepLinkURL: PropTypes.func.isRequired,
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
channelId: PropTypes.string,
|
||||
deepLinkURL: PropTypes.string,
|
||||
|
|
@ -353,7 +353,7 @@ export default class PostList extends PureComponent {
|
|||
};
|
||||
|
||||
this.showingPermalink = true;
|
||||
actions.showModalOverCurrentContext(screen, passProps, options);
|
||||
showModalOverCurrentContext(screen, passProps, options);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import PostList from './post_list';
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import * as NavigationActions from 'app/actions/navigation';
|
||||
import PostList from './post_list';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('PostList', () => {
|
||||
|
|
@ -18,7 +20,6 @@ describe('PostList', () => {
|
|||
refreshChannelWithRetry: jest.fn(),
|
||||
selectFocusedPostId: jest.fn(),
|
||||
setDeepLinkURL: jest.fn(),
|
||||
showModalOverCurrentContext: jest.fn(),
|
||||
},
|
||||
deepLinkURL: '',
|
||||
lastPostIndex: -1,
|
||||
|
|
@ -42,10 +43,12 @@ describe('PostList', () => {
|
|||
});
|
||||
|
||||
test('setting permalink deep link', () => {
|
||||
const showModalOverCurrentContext = jest.spyOn(NavigationActions, 'showModalOverCurrentContext');
|
||||
|
||||
wrapper.setProps({deepLinkURL: deepLinks.permalink});
|
||||
expect(baseProps.actions.setDeepLinkURL).toHaveBeenCalled();
|
||||
expect(baseProps.actions.selectFocusedPostId).toHaveBeenCalled();
|
||||
expect(baseProps.actions.showModalOverCurrentContext).toHaveBeenCalled();
|
||||
expect(showModalOverCurrentContext).toHaveBeenCalled();
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,15 @@ exports[`PostTextBox should match, full snapshot 1`] = `
|
|||
]
|
||||
}
|
||||
>
|
||||
<Connect(AttachmentButton)
|
||||
<AttachmentButton
|
||||
blurTextBox={[Function]}
|
||||
browseFileTypes="public.item"
|
||||
canBrowseFiles={true}
|
||||
canBrowsePhotoLibrary={true}
|
||||
canBrowseVideoLibrary={true}
|
||||
canTakePhoto={true}
|
||||
canTakeVideo={true}
|
||||
extraOptions={null}
|
||||
fileCount={0}
|
||||
maxFileCount={5}
|
||||
maxFileSize={1024}
|
||||
|
|
@ -55,6 +62,7 @@ exports[`PostTextBox should match, full snapshot 1`] = `
|
|||
}
|
||||
}
|
||||
uploadFiles={[Function]}
|
||||
validMimeTypes={Array []}
|
||||
/>
|
||||
<View
|
||||
style={
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
|||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
import {showModal, showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
import {addReaction} from 'app/actions/views/emoji';
|
||||
|
||||
import Reactions from './reactions';
|
||||
|
|
@ -64,8 +63,6 @@ function mapDispatchToProps(dispatch) {
|
|||
addReaction,
|
||||
getReactionsForPost,
|
||||
removeReaction,
|
||||
showModal,
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
|||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {showModal, showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
|
||||
import addReactionIcon from 'assets/images/icons/reaction.png';
|
||||
|
||||
import Reaction from './reaction';
|
||||
|
|
@ -23,8 +25,6 @@ export default class Reactions extends PureComponent {
|
|||
addReaction: PropTypes.func.isRequired,
|
||||
getReactionsForPost: PropTypes.func.isRequired,
|
||||
removeReaction: PropTypes.func.isRequired,
|
||||
showModal: PropTypes.func.isRequired,
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
currentUserId: PropTypes.string.isRequired,
|
||||
position: PropTypes.oneOf(['right', 'left']),
|
||||
|
|
@ -51,7 +51,7 @@ export default class Reactions extends PureComponent {
|
|||
}
|
||||
|
||||
handleAddReaction = preventDoubleTap(() => {
|
||||
const {actions, theme} = this.props;
|
||||
const {theme} = this.props;
|
||||
const {formatMessage} = this.context.intl;
|
||||
const screen = 'AddReaction';
|
||||
const title = formatMessage({id: 'mobile.post_info.add_reaction', defaultMessage: 'Add Reaction'});
|
||||
|
|
@ -62,7 +62,7 @@ export default class Reactions extends PureComponent {
|
|||
onEmojiPress: this.handleAddReactionToPost,
|
||||
};
|
||||
|
||||
actions.showModal(screen, title, passProps);
|
||||
showModal(screen, title, passProps);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ export default class Reactions extends PureComponent {
|
|||
};
|
||||
|
||||
showReactionList = () => {
|
||||
const {actions, postId} = this.props;
|
||||
const {postId} = this.props;
|
||||
|
||||
const screen = 'ReactionList';
|
||||
const passProps = {
|
||||
|
|
@ -94,7 +94,7 @@ export default class Reactions extends PureComponent {
|
|||
};
|
||||
|
||||
if (!this.onPressDetected) {
|
||||
actions.showModalOverCurrentContext(screen, passProps);
|
||||
showModalOverCurrentContext(screen, passProps);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getCurrentUrl} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {resetToTeams} from 'app/actions/navigation';
|
||||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
import {removeProtocol} from 'app/utils/url';
|
||||
|
||||
|
|
@ -23,12 +21,4 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
resetToTeams,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Root);
|
||||
export default connect(mapStateToProps)(Root);
|
||||
|
|
|
|||
|
|
@ -9,14 +9,12 @@ import {Platform} from 'react-native';
|
|||
import {Client4} from 'mattermost-redux/client';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import {resetToTeams} from 'app/actions/navigation';
|
||||
import {NavigationTypes} from 'app/constants';
|
||||
import {getTranslations} from 'app/i18n';
|
||||
|
||||
export default class Root extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
resetToTeams: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
children: PropTypes.node,
|
||||
excludeEvents: PropTypes.bool,
|
||||
currentUrl: PropTypes.string,
|
||||
|
|
@ -63,7 +61,7 @@ export default class Root extends PureComponent {
|
|||
}
|
||||
|
||||
navigateToTeamsPage = (screen) => {
|
||||
const {currentUrl, theme, actions} = this.props;
|
||||
const {currentUrl, theme} = this.props;
|
||||
const {intl} = this.refs.provider.getChildContext();
|
||||
|
||||
let passProps = {theme};
|
||||
|
|
@ -93,7 +91,7 @@ export default class Root extends PureComponent {
|
|||
|
||||
const title = intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'});
|
||||
|
||||
actions.resetToTeams(screen, title, passProps, options);
|
||||
resetToTeams(screen, title, passProps, options);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
|
|
@ -20,7 +19,6 @@ import {getConfig, getLicense, hasNewPermissions} from 'mattermost-redux/selecto
|
|||
import {haveITeamPermission} from 'mattermost-redux/selectors/entities/roles';
|
||||
import Permissions from 'mattermost-redux/constants/permissions';
|
||||
|
||||
import {showModal} from 'app/actions/navigation';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import {DeviceTypes, ViewTypes} from 'app/constants';
|
||||
|
||||
|
|
@ -77,14 +75,6 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
showModal,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
function areStatesEqual(next, prev) {
|
||||
const equalRoles = getCurrentUserRoles(prev) === getCurrentUserRoles(next);
|
||||
const equalChannels = next.entities.channels === prev.entities.channels;
|
||||
|
|
@ -95,4 +85,4 @@ function areStatesEqual(next, prev) {
|
|||
return equalChannels && equalConfig && equalRoles && equalUsers && equalFav;
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, null, {pure: true, areStatesEqual})(List);
|
||||
export default connect(mapStateToProps, null, null, {pure: true, areStatesEqual})(List);
|
||||
|
|
|
|||
|
|
@ -21,14 +21,14 @@ import {General} from 'mattermost-redux/constants';
|
|||
import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
|
||||
import ChannelItem from 'app/components/sidebars/main/channels_list/channel_item';
|
||||
import {paddingLeft as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {DeviceTypes, ListTypes} from 'app/constants';
|
||||
import {SidebarSectionTypes} from 'app/constants/view';
|
||||
|
||||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
|
||||
import {paddingLeft as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import {showModal} from 'app/actions/navigation';
|
||||
|
||||
const VIEWABILITY_CONFIG = {
|
||||
...ListTypes.VISIBILITY_CONFIG_DEFAULTS,
|
||||
|
|
@ -50,9 +50,6 @@ export default class List extends PureComponent {
|
|||
orderedChannelIds: PropTypes.array.isRequired,
|
||||
previewChannel: PropTypes.func,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
showModal: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -224,7 +221,6 @@ export default class List extends PureComponent {
|
|||
};
|
||||
|
||||
goToCreatePublicChannel = preventDoubleTap(() => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'CreateChannel';
|
||||
const title = intl.formatMessage({id: 'mobile.create_channel.public', defaultMessage: 'New Public Channel'});
|
||||
|
|
@ -233,11 +229,10 @@ export default class List extends PureComponent {
|
|||
closeButton: this.closeButton,
|
||||
};
|
||||
|
||||
actions.showModal(screen, title, passProps);
|
||||
showModal(screen, title, passProps);
|
||||
});
|
||||
|
||||
goToCreatePrivateChannel = preventDoubleTap(() => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'CreateChannel';
|
||||
const title = intl.formatMessage({id: 'mobile.create_channel.private', defaultMessage: 'New Private Channel'});
|
||||
|
|
@ -246,11 +241,10 @@ export default class List extends PureComponent {
|
|||
closeButton: this.closeButton,
|
||||
};
|
||||
|
||||
actions.showModal(screen, title, passProps);
|
||||
showModal(screen, title, passProps);
|
||||
});
|
||||
|
||||
goToDirectMessages = preventDoubleTap(() => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'MoreDirectMessages';
|
||||
const title = intl.formatMessage({id: 'mobile.more_dms.title', defaultMessage: 'New Conversation'});
|
||||
|
|
@ -264,11 +258,10 @@ export default class List extends PureComponent {
|
|||
},
|
||||
};
|
||||
|
||||
actions.showModal(screen, title, passProps, options);
|
||||
showModal(screen, title, passProps, options);
|
||||
});
|
||||
|
||||
goToMoreChannels = preventDoubleTap(() => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'MoreChannels';
|
||||
const title = intl.formatMessage({id: 'more_channels.title', defaultMessage: 'More Channels'});
|
||||
|
|
@ -276,7 +269,7 @@ export default class List extends PureComponent {
|
|||
closeButton: this.closeButton,
|
||||
};
|
||||
|
||||
actions.showModal(screen, title, passProps);
|
||||
showModal(screen, title, passProps);
|
||||
});
|
||||
|
||||
keyExtractor = (item) => item.id || item;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {connect} from 'react-redux';
|
|||
import {getCurrentTeamId, getMySortedTeamIds, getJoinableTeamIds} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {showModal} from 'app/actions/navigation';
|
||||
import {handleTeamChange} from 'app/actions/views/select_team';
|
||||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
|
||||
|
|
@ -28,7 +27,6 @@ function mapDispatchToProps(dispatch) {
|
|||
return {
|
||||
actions: bindActionCreators({
|
||||
handleTeamChange,
|
||||
showModal,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
|||
import {removeProtocol} from 'app/utils/url';
|
||||
import tracker from 'app/utils/time_tracker';
|
||||
import telemetry from 'app/telemetry';
|
||||
import {showModal} from 'app/actions/navigation';
|
||||
|
||||
import TeamsListItem from './teams_list_item';
|
||||
|
||||
|
|
@ -36,7 +37,6 @@ export default class TeamsList extends PureComponent {
|
|||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
handleTeamChange: PropTypes.func.isRequired,
|
||||
showModal: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
closeChannelDrawer: PropTypes.func.isRequired,
|
||||
currentTeamId: PropTypes.string.isRequired,
|
||||
|
|
@ -86,7 +86,7 @@ export default class TeamsList extends PureComponent {
|
|||
|
||||
goToSelectTeam = preventDoubleTap(async () => {
|
||||
const {intl} = this.context;
|
||||
const {theme, actions} = this.props;
|
||||
const {theme} = this.props;
|
||||
const {serverUrl} = this.state;
|
||||
const screen = 'SelectTeam';
|
||||
const title = intl.formatMessage({id: 'mobile.routes.selectTeam', defaultMessage: 'Select Team'});
|
||||
|
|
@ -103,7 +103,7 @@ export default class TeamsList extends PureComponent {
|
|||
},
|
||||
};
|
||||
|
||||
actions.showModal(screen, title, passProps, options);
|
||||
showModal(screen, title, passProps, options);
|
||||
});
|
||||
|
||||
keyExtractor = (item) => {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,6 @@ import {logout, setStatus} from 'mattermost-redux/actions/users';
|
|||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentUser, getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {
|
||||
showModal,
|
||||
showModalOverCurrentContext,
|
||||
dismissModal,
|
||||
} from 'app/actions/navigation';
|
||||
|
||||
import SettingsSidebar from './settings_sidebar';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
@ -32,9 +26,6 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
logout,
|
||||
setStatus,
|
||||
showModal,
|
||||
showModalOverCurrentContext,
|
||||
dismissModal,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {confirmOutOfOfficeDisabled} from 'app/utils/status';
|
|||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {showModal, showModalOverCurrentContext, dismissModal} from 'app/actions/navigation';
|
||||
|
||||
import DrawerItem from './drawer_item';
|
||||
import UserInfo from './user_info';
|
||||
|
|
@ -35,9 +36,6 @@ export default class SettingsDrawer extends PureComponent {
|
|||
actions: PropTypes.shape({
|
||||
logout: PropTypes.func.isRequired,
|
||||
setStatus: PropTypes.func.isRequired,
|
||||
showModal: PropTypes.func.isRequired,
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
dismissModal: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
blurPostTextBox: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
|
|
@ -127,7 +125,6 @@ export default class SettingsDrawer extends PureComponent {
|
|||
};
|
||||
|
||||
handleSetStatus = preventDoubleTap(() => {
|
||||
const {actions} = this.props;
|
||||
const items = [{
|
||||
action: () => this.setStatus(General.ONLINE),
|
||||
text: {
|
||||
|
|
@ -154,7 +151,7 @@ export default class SettingsDrawer extends PureComponent {
|
|||
},
|
||||
}];
|
||||
|
||||
actions.showModalOverCurrentContext('OptionsModal', {items});
|
||||
showModalOverCurrentContext('OptionsModal', {items});
|
||||
});
|
||||
|
||||
goToEditProfile = preventDoubleTap(() => {
|
||||
|
|
@ -216,7 +213,6 @@ export default class SettingsDrawer extends PureComponent {
|
|||
openModal = (screen, title, passProps) => {
|
||||
this.closeSettingsSidebar();
|
||||
|
||||
const {actions} = this.props;
|
||||
const options = {
|
||||
topBar: {
|
||||
leftButtons: [{
|
||||
|
|
@ -227,7 +223,7 @@ export default class SettingsDrawer extends PureComponent {
|
|||
};
|
||||
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
actions.showModal(screen, title, passProps, options);
|
||||
showModal(screen, title, passProps, options);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -354,10 +350,10 @@ export default class SettingsDrawer extends PureComponent {
|
|||
};
|
||||
|
||||
setStatus = (status) => {
|
||||
const {status: currentUserStatus, actions} = this.props;
|
||||
const {status: currentUserStatus} = this.props;
|
||||
|
||||
if (currentUserStatus === General.OUT_OF_OFFICE) {
|
||||
actions.dismissModal();
|
||||
dismissModal();
|
||||
this.closeSettingsSidebar();
|
||||
this.confirmReset(status);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ class GlobalEventHandler {
|
|||
|
||||
handleInAppNotification = (notification) => {
|
||||
const {data} = notification;
|
||||
const {dispatch, getState} = this.store;
|
||||
const {getState} = this.store;
|
||||
const state = getState();
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
|
||||
|
|
@ -258,7 +258,7 @@ class GlobalEventHandler {
|
|||
};
|
||||
|
||||
EventEmitter.emit(NavigationTypes.NAVIGATION_SHOW_OVERLAY);
|
||||
dispatch(showOverlay(screen, passProps));
|
||||
showOverlay(screen, passProps);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,21 +9,19 @@ import {loadMe} from 'mattermost-redux/actions/users';
|
|||
|
||||
import {resetToChannel, resetToSelectServer} from 'app/actions/navigation';
|
||||
import {setDeepLinkURL} from 'app/actions/views/root';
|
||||
import initialState from 'app/initial_state';
|
||||
import {getAppCredentials} from 'app/init/credentials';
|
||||
import emmProvider from 'app/init/emm_provider';
|
||||
import 'app/init/device';
|
||||
import 'app/init/fetch';
|
||||
import globalEventHandler from 'app/init/global_event_handler';
|
||||
import {registerScreens} from 'app/screens';
|
||||
import configureStore from 'app/store';
|
||||
import store from 'app/store';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import telemetry from 'app/telemetry';
|
||||
import pushNotificationsUtils from 'app/utils/push_notifications';
|
||||
|
||||
const {MattermostShare} = NativeModules;
|
||||
const sharedExtensionStarted = Platform.OS === 'android' && MattermostShare.isOpened;
|
||||
export const store = configureStore(initialState);
|
||||
|
||||
const init = async () => {
|
||||
const credentials = await getAppCredentials();
|
||||
|
|
@ -57,9 +55,9 @@ const launchApp = async (credentials) => {
|
|||
|
||||
if (credentials) {
|
||||
store.dispatch(loadMe());
|
||||
store.dispatch(resetToChannel({skipMetrics: true}));
|
||||
resetToChannel({skipMetrics: true});
|
||||
} else {
|
||||
store.dispatch(resetToSelectServer(emmProvider.allowOtherServers));
|
||||
resetToSelectServer(emmProvider.allowOtherServers);
|
||||
}
|
||||
|
||||
telemetry.startSinceLaunch(['start:splash_screen']);
|
||||
|
|
|
|||
|
|
@ -12,13 +12,10 @@ import {Navigation} from 'react-native-navigation';
|
|||
import EmojiPicker from 'app/components/emoji_picker';
|
||||
import {emptyFunction} from 'app/utils/general';
|
||||
import {setNavigatorStyles} from 'app/utils/theme';
|
||||
import {dismissModal, setButtons} from 'app/actions/navigation';
|
||||
|
||||
export default class AddReaction extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
dismissModal: PropTypes.func.isRequired,
|
||||
setButtons: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
componentId: PropTypes.string,
|
||||
closeButton: PropTypes.object,
|
||||
onEmojiPress: PropTypes.func,
|
||||
|
|
@ -36,7 +33,7 @@ export default class AddReaction extends PureComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
props.actions.setButtons(props.componentId, {
|
||||
setButtons(props.componentId, {
|
||||
leftButtons: [{...this.leftButton, icon: props.closeButton}],
|
||||
});
|
||||
}
|
||||
|
|
@ -58,7 +55,7 @@ export default class AddReaction extends PureComponent {
|
|||
}
|
||||
|
||||
close = () => {
|
||||
this.props.actions.dismissModal();
|
||||
dismissModal();
|
||||
};
|
||||
|
||||
handleEmojiPress = (emoji) => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {dismissModal, setButtons} from 'app/actions/navigation';
|
||||
|
||||
import AddReaction from './add_reaction';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
@ -16,13 +13,4 @@ function mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
dismissModal,
|
||||
setButtons,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AddReaction);
|
||||
export default connect(mapStateToProps)(AddReaction);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import PostTextbox from 'app/components/post_textbox';
|
|||
import SafeAreaView from 'app/components/safe_area_view';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {DeviceTypes} from 'app/constants';
|
||||
import {peek} from 'app/actions/navigation';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
|
|
@ -26,10 +27,9 @@ const CHANNEL_POST_TEXTBOX_VALUE_CHANGE = 'onChannelTextBoxValueChange';
|
|||
|
||||
export default class ChannelIOS extends ChannelBase {
|
||||
previewChannel = (passProps, options) => {
|
||||
const {actions} = this.props;
|
||||
const screen = 'ChannelPeek';
|
||||
|
||||
actions.peek(screen, passProps, options);
|
||||
peek(screen, passProps, options);
|
||||
};
|
||||
|
||||
optionalProps = {previewChannel: this.previewChannel};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
|
||||
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
|
@ -25,6 +24,11 @@ import PushNotifications from 'app/push_notifications';
|
|||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import tracker from 'app/utils/time_tracker';
|
||||
import telemetry from 'app/telemetry';
|
||||
import {
|
||||
goToScreen,
|
||||
showModalOverCurrentContext,
|
||||
mergeNavigationOptions,
|
||||
} from 'app/actions/navigation';
|
||||
|
||||
import LocalConfig from 'assets/config';
|
||||
|
||||
|
|
@ -38,9 +42,6 @@ export default class ChannelBase extends PureComponent {
|
|||
selectDefaultTeam: PropTypes.func.isRequired,
|
||||
selectInitialChannel: PropTypes.func.isRequired,
|
||||
recordLoadTime: PropTypes.func.isRequired,
|
||||
peek: PropTypes.func.isRequired,
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
getChannelStats: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
componentId: PropTypes.string.isRequired,
|
||||
|
|
@ -68,11 +69,12 @@ export default class ChannelBase extends PureComponent {
|
|||
this.postTextbox = React.createRef();
|
||||
this.keyboardTracker = React.createRef();
|
||||
|
||||
Navigation.mergeOptions(props.componentId, {
|
||||
const options = {
|
||||
layout: {
|
||||
backgroundColor: props.theme.centerChannelBg,
|
||||
},
|
||||
});
|
||||
};
|
||||
mergeNavigationOptions(props.componentId, options);
|
||||
|
||||
if (LocalConfig.EnableMobileClientUpgrade && !ClientUpgradeListener) {
|
||||
ClientUpgradeListener = require('app/components/client_upgrade_listener').default;
|
||||
|
|
@ -113,11 +115,12 @@ export default class ChannelBase extends PureComponent {
|
|||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.theme !== nextProps.theme) {
|
||||
Navigation.mergeOptions(this.props.componentId, {
|
||||
const options = {
|
||||
layout: {
|
||||
backgroundColor: nextProps.theme.centerChannelBg,
|
||||
},
|
||||
});
|
||||
};
|
||||
mergeNavigationOptions(this.props.componentId, options);
|
||||
}
|
||||
|
||||
if (nextProps.currentTeamId && this.props.currentTeamId !== nextProps.currentTeamId) {
|
||||
|
|
@ -177,7 +180,7 @@ export default class ChannelBase extends PureComponent {
|
|||
|
||||
showTermsOfServiceModal = async () => {
|
||||
const {intl} = this.context;
|
||||
const {actions, theme} = this.props;
|
||||
const {theme} = this.props;
|
||||
const closeButton = await MaterialIcon.getImageSource('close', 20, theme.sidebarHeaderTextColor);
|
||||
const screen = 'TermsOfService';
|
||||
const passProps = {closeButton};
|
||||
|
|
@ -196,11 +199,10 @@ export default class ChannelBase extends PureComponent {
|
|||
},
|
||||
};
|
||||
|
||||
actions.showModalOverCurrentContext(screen, passProps, options);
|
||||
showModalOverCurrentContext(screen, passProps, options);
|
||||
};
|
||||
|
||||
goToChannelInfo = preventDoubleTap(() => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'ChannelInfo';
|
||||
const title = intl.formatMessage({id: 'mobile.routes.channelInfo', defaultMessage: 'Info'});
|
||||
|
|
@ -208,7 +210,7 @@ export default class ChannelBase extends PureComponent {
|
|||
Keyboard.dismiss();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
actions.goToScreen(screen, title);
|
||||
goToScreen(screen, title);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ import AwesomeIcon from 'react-native-vector-icons/FontAwesome';
|
|||
|
||||
import {preventDoubleTap} from 'app/utils/tap';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {showSearchModal} from 'app/actions/navigation';
|
||||
|
||||
export default class ChannelSearchButton extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
clearSearch: PropTypes.func.isRequired,
|
||||
showSearchModal: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
theme: PropTypes.object,
|
||||
};
|
||||
|
|
@ -27,7 +27,7 @@ export default class ChannelSearchButton extends PureComponent {
|
|||
|
||||
Keyboard.dismiss();
|
||||
await actions.clearSearch();
|
||||
await actions.showSearchModal();
|
||||
showSearchModal();
|
||||
});
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -6,15 +6,12 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {clearSearch} from 'mattermost-redux/actions/search';
|
||||
|
||||
import {showSearchModal} from 'app/actions/navigation';
|
||||
|
||||
import ChannelSearchButton from './channel_search_button';
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
clearSearch,
|
||||
showSearchModal,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {ViewTypes} from 'app/constants';
|
|||
import tracker from 'app/utils/time_tracker';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import telemetry from 'app/telemetry';
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
|
||||
let ChannelIntro = null;
|
||||
let LoadMorePosts = null;
|
||||
|
|
@ -32,7 +33,6 @@ export default class ChannelPostList extends PureComponent {
|
|||
selectPost: PropTypes.func.isRequired,
|
||||
recordLoadTime: PropTypes.func.isRequired,
|
||||
refreshChannelWithRetry: PropTypes.func.isRequired,
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
channelId: PropTypes.string.isRequired,
|
||||
channelRefreshingFailed: PropTypes.bool,
|
||||
|
|
@ -119,7 +119,7 @@ export default class ChannelPostList extends PureComponent {
|
|||
};
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
actions.goToScreen(screen, title, passProps);
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'
|
|||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {goToScreen} from 'app/actions/navigation';
|
||||
import {loadPostsIfNecessaryWithRetry, loadThreadIfNecessary, increasePostVisibility, refreshChannelWithRetry} from 'app/actions/views/channel';
|
||||
import {recordLoadTime} from 'app/actions/views/root';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
|
@ -45,7 +44,6 @@ function mapDispatchToProps(dispatch) {
|
|||
selectPost,
|
||||
recordLoadTime,
|
||||
refreshChannelWithRetry,
|
||||
goToScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import {
|
|||
import {connection} from 'app/actions/device';
|
||||
import {recordLoadTime} from 'app/actions/views/root';
|
||||
import {selectDefaultTeam} from 'app/actions/views/select_team';
|
||||
import {peek, goToScreen, showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import Channel from './channel';
|
||||
|
|
@ -51,9 +50,6 @@ function mapDispatchToProps(dispatch) {
|
|||
recordLoadTime,
|
||||
startPeriodicStatusUpdates,
|
||||
stopPeriodicStatusUpdates,
|
||||
peek,
|
||||
goToScreen,
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {Navigation} from 'react-native-navigation';
|
|||
import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {filterProfilesMatchingTerm} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import Loading from 'app/components/loading';
|
||||
import CustomList, {FLATLIST, SECTIONLIST} from 'app/components/custom_list';
|
||||
|
|
@ -30,6 +31,7 @@ import {
|
|||
setNavigatorStyles,
|
||||
getKeyboardAppearanceFromTheme,
|
||||
} from 'app/utils/theme';
|
||||
import {popTopScreen, setButtons} from 'app/actions/navigation';
|
||||
|
||||
export default class ChannelAddMembers extends PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -38,8 +40,6 @@ export default class ChannelAddMembers extends PureComponent {
|
|||
getProfilesNotInChannel: PropTypes.func.isRequired,
|
||||
handleAddChannelMembers: PropTypes.func.isRequired,
|
||||
searchProfiles: PropTypes.func.isRequired,
|
||||
setButtons: PropTypes.func.isRequired,
|
||||
popTopScreen: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
componentId: PropTypes.string,
|
||||
currentChannelId: PropTypes.string.isRequired,
|
||||
|
|
@ -82,7 +82,7 @@ export default class ChannelAddMembers extends PureComponent {
|
|||
showAsAction: 'always',
|
||||
};
|
||||
|
||||
props.actions.setButtons(props.componentId, {
|
||||
setButtons(props.componentId, {
|
||||
rightButtons: [this.addButton],
|
||||
});
|
||||
}
|
||||
|
|
@ -121,12 +121,12 @@ export default class ChannelAddMembers extends PureComponent {
|
|||
};
|
||||
|
||||
close = () => {
|
||||
this.props.actions.popTopScreen();
|
||||
popTopScreen();
|
||||
};
|
||||
|
||||
enableAddOption = (enabled) => {
|
||||
const {actions, componentId} = this.props;
|
||||
actions.setButtons(componentId, {
|
||||
const {componentId} = this.props;
|
||||
setButtons(componentId, {
|
||||
rightButtons: [{...this.addButton, enabled}],
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import React from 'react';
|
|||
|
||||
import {Preferences} from 'mattermost-redux/constants';
|
||||
|
||||
import * as NavigationActions from 'app/actions/navigation';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import ChannelAddMembers from './channel_add_members';
|
||||
|
|
@ -16,8 +17,6 @@ describe('ChannelAddMembers', () => {
|
|||
getProfilesNotInChannel: jest.fn().mockResolvedValue({}),
|
||||
handleAddChannelMembers: jest.fn().mockResolvedValue({}),
|
||||
searchProfiles: jest.fn().mockResolvedValue({data: []}),
|
||||
setButtons: jest.fn(),
|
||||
popTopScreen: jest.fn(),
|
||||
},
|
||||
currentChannelId: 'current_channel_id',
|
||||
currentTeamId: 'current_team_id',
|
||||
|
|
@ -29,15 +28,17 @@ describe('ChannelAddMembers', () => {
|
|||
};
|
||||
|
||||
test('should render without error and call functions on mount', () => {
|
||||
const setButtons = jest.spyOn(NavigationActions, 'setButtons');
|
||||
|
||||
shallowWithIntl(<ChannelAddMembers {...baseProps}/>);
|
||||
|
||||
expect(baseProps.actions.getTeamStats).toBeCalledTimes(1);
|
||||
expect(baseProps.actions.getTeamStats).toBeCalledWith(baseProps.currentTeamId);
|
||||
|
||||
const button = {enabled: false, id: 'add-members', text: 'Add', showAsAction: 'always'};
|
||||
expect(baseProps.actions.setButtons).toBeCalledTimes(2);
|
||||
expect(baseProps.actions.setButtons.mock.calls[0][0]).toEqual(baseProps.componentId, {rightButtons: [button]});
|
||||
expect(baseProps.actions.setButtons.mock.calls[1][0]).toEqual(baseProps.componentId, {rightButtons: [button]});
|
||||
expect(setButtons).toBeCalledTimes(2);
|
||||
expect(setButtons.mock.calls[0][0]).toEqual(baseProps.componentId, {rightButtons: [button]});
|
||||
expect(setButtons.mock.calls[1][0]).toEqual(baseProps.componentId, {rightButtons: [button]});
|
||||
});
|
||||
|
||||
test('should match state on clearSearch', () => {
|
||||
|
|
@ -49,11 +50,13 @@ describe('ChannelAddMembers', () => {
|
|||
wrapper.setState({term: '', searchResults: []});
|
||||
});
|
||||
|
||||
test('should call props.popTopScreen on close', () => {
|
||||
test('should call popTopScreen on close', () => {
|
||||
const popTopScreen = jest.spyOn(NavigationActions, 'popTopScreen');
|
||||
|
||||
const wrapper = shallowWithIntl(<ChannelAddMembers {...baseProps}/>);
|
||||
|
||||
wrapper.instance().close();
|
||||
expect(baseProps.actions.popTopScreen).toBeCalledTimes(1);
|
||||
expect(popTopScreen).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should match state on onProfilesLoaded', () => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
|||
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUserId, getProfilesNotInCurrentChannel} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {setButtons, popTopScreen} from 'app/actions/navigation';
|
||||
import {handleAddChannelMembers} from 'app/actions/views/channel_add_members';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import ChannelAddMembers from './channel_add_members';
|
||||
|
|
@ -37,8 +36,6 @@ function mapDispatchToProps(dispatch) {
|
|||
getProfilesNotInChannel,
|
||||
handleAddChannelMembers,
|
||||
searchProfiles,
|
||||
setButtons,
|
||||
popTopScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -283,6 +283,7 @@ exports[`channel_info_header should match snapshot 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
onChannelLinkPress={[MockFunction]}
|
||||
onPermalinkPress={[MockFunction]}
|
||||
textStyles={
|
||||
Object {
|
||||
|
|
@ -718,6 +719,7 @@ exports[`channel_info_header should match snapshot when DM and hasGuests 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
onChannelLinkPress={[MockFunction]}
|
||||
onPermalinkPress={[MockFunction]}
|
||||
textStyles={
|
||||
Object {
|
||||
|
|
@ -1153,6 +1155,7 @@ exports[`channel_info_header should match snapshot when GM and hasGuests 1`] = `
|
|||
},
|
||||
}
|
||||
}
|
||||
onChannelLinkPress={[MockFunction]}
|
||||
onPermalinkPress={[MockFunction]}
|
||||
textStyles={
|
||||
Object {
|
||||
|
|
@ -1560,6 +1563,7 @@ exports[`channel_info_header should match snapshot when is group constrained 1`]
|
|||
},
|
||||
}
|
||||
}
|
||||
onChannelLinkPress={[MockFunction]}
|
||||
onPermalinkPress={[MockFunction]}
|
||||
textStyles={
|
||||
Object {
|
||||
|
|
@ -2016,6 +2020,7 @@ exports[`channel_info_header should match snapshot when public channel and hasGu
|
|||
},
|
||||
}
|
||||
}
|
||||
onChannelLinkPress={[MockFunction]}
|
||||
onPermalinkPress={[MockFunction]}
|
||||
textStyles={
|
||||
Object {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import {preventDoubleTap} from 'app/utils/tap';
|
|||
import {alertErrorWithFallback} from 'app/utils/general';
|
||||
import {changeOpacity, makeStyleSheetFromTheme, setNavigatorStyles} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {goToScreen, popTopScreen, showModalOverCurrentContext} from 'app/actions/navigation';
|
||||
|
||||
import pinIcon from 'assets/images/channel_info/pin.png';
|
||||
|
||||
import ChannelInfoHeader from './channel_info_header';
|
||||
|
|
@ -41,10 +43,6 @@ export default class ChannelInfo extends PureComponent {
|
|||
selectPenultimateChannel: PropTypes.func.isRequired,
|
||||
handleSelectChannel: PropTypes.func.isRequired,
|
||||
setChannelDisplayName: PropTypes.func.isRequired,
|
||||
popTopScreen: PropTypes.func.isRequired,
|
||||
goToScreen: PropTypes.func.isRequired,
|
||||
dismissModal: PropTypes.func.isRequired,
|
||||
showModalOverCurrentContext: PropTypes.func.isRequired,
|
||||
}),
|
||||
componentId: PropTypes.string,
|
||||
viewArchivedChannels: PropTypes.bool.isRequired,
|
||||
|
|
@ -120,31 +118,30 @@ export default class ChannelInfo extends PureComponent {
|
|||
actions.setChannelDisplayName('');
|
||||
}
|
||||
|
||||
actions.popTopScreen();
|
||||
popTopScreen();
|
||||
};
|
||||
|
||||
goToChannelAddMembers = preventDoubleTap(() => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'ChannelAddMembers';
|
||||
const title = intl.formatMessage({id: 'channel_header.addMembers', defaultMessage: 'Add Members'});
|
||||
|
||||
actions.goToScreen(screen, title);
|
||||
goToScreen(screen, title);
|
||||
});
|
||||
|
||||
goToChannelMembers = preventDoubleTap(() => {
|
||||
const {actions, canManageUsers} = this.props;
|
||||
const {canManageUsers} = this.props;
|
||||
const {intl} = this.context;
|
||||
const id = canManageUsers ? t('channel_header.manageMembers') : t('channel_header.viewMembers');
|
||||
const defaultMessage = canManageUsers ? 'Manage Members' : 'View Members';
|
||||
const screen = 'ChannelMembers';
|
||||
const title = intl.formatMessage({id, defaultMessage});
|
||||
|
||||
actions.goToScreen(screen, title);
|
||||
goToScreen(screen, title);
|
||||
});
|
||||
|
||||
goToPinnedPosts = preventDoubleTap(() => {
|
||||
const {actions, currentChannel} = this.props;
|
||||
const {currentChannel} = this.props;
|
||||
const {formatMessage} = this.context.intl;
|
||||
const id = t('channel_header.pinnedPosts');
|
||||
const defaultMessage = 'Pinned Posts';
|
||||
|
|
@ -154,18 +151,17 @@ export default class ChannelInfo extends PureComponent {
|
|||
currentChannelId: currentChannel.id,
|
||||
};
|
||||
|
||||
actions.goToScreen(screen, title, passProps);
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
|
||||
handleChannelEdit = preventDoubleTap(() => {
|
||||
const {actions} = this.props;
|
||||
const {intl} = this.context;
|
||||
const id = t('mobile.channel_info.edit');
|
||||
const defaultMessage = 'Edit Channel';
|
||||
const screen = 'EditChannel';
|
||||
const title = intl.formatMessage({id, defaultMessage});
|
||||
|
||||
actions.goToScreen(screen, title);
|
||||
goToScreen(screen, title);
|
||||
});
|
||||
|
||||
handleLeave = () => {
|
||||
|
|
@ -324,7 +320,7 @@ export default class ChannelInfo extends PureComponent {
|
|||
actions.selectFocusedPostId(postId);
|
||||
|
||||
this.showingPermalink = true;
|
||||
actions.showModalOverCurrentContext(screen, passProps, options);
|
||||
showModalOverCurrentContext(screen, passProps, options);
|
||||
};
|
||||
|
||||
renderViewOrManageMembersRow = () => {
|
||||
|
|
@ -489,7 +485,6 @@ export default class ChannelInfo extends PureComponent {
|
|||
theme,
|
||||
isBot,
|
||||
isLandscape,
|
||||
actions: {popToRoot},
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
|
@ -530,7 +525,6 @@ export default class ChannelInfo extends PureComponent {
|
|||
isBot={isBot}
|
||||
hasGuests={currentChannelGuestCount > 0}
|
||||
isGroupConstrained={currentChannel.group_constrained}
|
||||
popToRoot={popToRoot}
|
||||
/>
|
||||
}
|
||||
<View style={style.rowsContainer}>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import BottomSheet from 'app/utils/bottom_sheet';
|
|||
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {popToRoot} from 'app/actions/navigation';
|
||||
|
||||
export default class ChannelInfoHeader extends React.PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -42,7 +43,6 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
hasGuests: PropTypes.bool.isRequired,
|
||||
isGroupConstrained: PropTypes.bool,
|
||||
timeZone: PropTypes.string,
|
||||
popToRoot: PropTypes.func,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -138,7 +138,6 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
isBot,
|
||||
isGroupConstrained,
|
||||
timeZone,
|
||||
popToRoot,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
|
|
|||
|
|
@ -32,13 +32,6 @@ import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general
|
|||
import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone';
|
||||
import {getUserCurrentTimezone} from 'mattermost-redux/utils/timezone_utils';
|
||||
|
||||
import {
|
||||
popTopScreen,
|
||||
goToScreen,
|
||||
popToRoot,
|
||||
dismissModal,
|
||||
showModalOverCurrentContext,
|
||||
} from 'app/actions/navigation';
|
||||
import {
|
||||
closeDMChannel,
|
||||
closeGMChannel,
|
||||
|
|
@ -145,11 +138,6 @@ function mapDispatchToProps(dispatch) {
|
|||
selectPenultimateChannel,
|
||||
setChannelDisplayName,
|
||||
handleSelectChannel,
|
||||
popTopScreen,
|
||||
goToScreen,
|
||||
popToRoot,
|
||||
dismissModal,
|
||||
showModalOverCurrentContext,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {Navigation} from 'react-native-navigation';
|
|||
import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
import {General} from 'mattermost-redux/constants';
|
||||
import {filterProfilesMatchingTerm} from 'mattermost-redux/utils/user_utils';
|
||||
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import Loading from 'app/components/loading';
|
||||
import CustomList, {FLATLIST, SECTIONLIST} from 'app/components/custom_list';
|
||||
|
|
@ -30,6 +31,7 @@ import {
|
|||
setNavigatorStyles,
|
||||
getKeyboardAppearanceFromTheme,
|
||||
} from 'app/utils/theme';
|
||||
import {popTopScreen, setButtons} from 'app/actions/navigation';
|
||||
|
||||
export default class ChannelMembers extends PureComponent {
|
||||
static propTypes = {
|
||||
|
|
@ -37,8 +39,6 @@ export default class ChannelMembers extends PureComponent {
|
|||
getProfilesInChannel: PropTypes.func.isRequired,
|
||||
handleRemoveChannelMembers: PropTypes.func.isRequired,
|
||||
searchProfiles: PropTypes.func.isRequired,
|
||||
setButtons: PropTypes.func.isRequired,
|
||||
popTopScreen: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
componentId: PropTypes.string,
|
||||
canManageUsers: PropTypes.bool.isRequired,
|
||||
|
|
@ -78,7 +78,7 @@ export default class ChannelMembers extends PureComponent {
|
|||
};
|
||||
|
||||
if (props.canManageUsers) {
|
||||
props.actions.setButtons(props.componentId, {
|
||||
setButtons(props.componentId, {
|
||||
rightButtons: [this.removeButton],
|
||||
});
|
||||
}
|
||||
|
|
@ -113,13 +113,13 @@ export default class ChannelMembers extends PureComponent {
|
|||
};
|
||||
|
||||
close = () => {
|
||||
this.props.actions.popTopScreen();
|
||||
popTopScreen();
|
||||
};
|
||||
|
||||
enableRemoveOption = (enabled) => {
|
||||
const {actions, canManageUsers, componentId} = this.props;
|
||||
const {canManageUsers, componentId} = this.props;
|
||||
if (canManageUsers) {
|
||||
actions.setButtons(componentId, {
|
||||
setButtons(componentId, {
|
||||
rightButtons: [{...this.removeButton, enabled}],
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ describe('ChannelMembers', () => {
|
|||
getProfilesInChannel: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
handleRemoveChannelMembers: jest.fn(),
|
||||
searchProfiles: jest.fn(),
|
||||
setButtons: jest.fn(),
|
||||
popTopScreen: jest.fn(),
|
||||
},
|
||||
componentId: 'component-id',
|
||||
isLandscape: false,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {getCurrentChannel, canManageChannelMembers} from 'mattermost-redux/selec
|
|||
import {makeGetProfilesInChannel} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getProfilesInChannel, searchProfiles} from 'mattermost-redux/actions/users';
|
||||
|
||||
import {setButtons, popTopScreen} from 'app/actions/navigation';
|
||||
import {handleRemoveChannelMembers} from 'app/actions/views/channel_members';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
import ChannelMembers from './channel_members';
|
||||
|
|
@ -43,8 +42,6 @@ function mapDispatchToProps(dispatch) {
|
|||
getProfilesInChannel,
|
||||
handleRemoveChannelMembers,
|
||||
searchProfiles,
|
||||
setButtons,
|
||||
popTopScreen,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue