Merge branch 'master' into mark-as-unread
This commit is contained in:
commit
4899c2f769
26 changed files with 2741 additions and 2505 deletions
|
|
@ -123,7 +123,7 @@ android {
|
|||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5"
|
||||
versionCode 238
|
||||
versionCode 239
|
||||
versionName "1.24.0"
|
||||
multiDexEnabled = true
|
||||
ndk {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
import {addChannelMember} from 'mattermost-redux/actions/channels';
|
||||
|
||||
export function handleAddChannelMembers(channelId, members) {
|
||||
return async (dispatch, getState) => {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
const requests = members.map((m) => dispatch(addChannelMember(channelId, m, getState)));
|
||||
const requests = members.map((m) => dispatch(addChannelMember(channelId, m)));
|
||||
|
||||
return await Promise.all(requests);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ function makeMapStateToProps() {
|
|||
return function mapStateToProps(state, ownProps) {
|
||||
const post = ownProps.post || getPost(state, ownProps.postId);
|
||||
const previousPost = getPost(state, ownProps.previousPostId);
|
||||
const beforePrevPost = getPost(state, ownProps.beforePrevPostId);
|
||||
|
||||
const myPreferences = getMyPreferences(state);
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
|
|
@ -84,6 +85,8 @@ function makeMapStateToProps() {
|
|||
isFlagged: isPostFlagged(post.id, myPreferences),
|
||||
isCommentMention,
|
||||
isLandscape: isLandscape(state),
|
||||
previousPostExists: Boolean(previousPost),
|
||||
beforePrevPostUserId: (beforePrevPost ? beforePrevPost.user_id : null),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ export default class Post extends PureComponent {
|
|||
location: PropTypes.string,
|
||||
isBot: PropTypes.bool,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
previousPostExists: PropTypes.bool,
|
||||
beforePrevPostUserId: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -250,6 +252,8 @@ export default class Post extends PureComponent {
|
|||
skipPinnedHeader,
|
||||
location,
|
||||
isLandscape,
|
||||
previousPostExists,
|
||||
beforePrevPostUserId,
|
||||
} = this.props;
|
||||
|
||||
if (!post) {
|
||||
|
|
@ -299,6 +303,8 @@ export default class Post extends PureComponent {
|
|||
onUsernamePress={onUsernamePress}
|
||||
renderReplies={renderReplies}
|
||||
theme={theme}
|
||||
previousPostExists={previousPostExists}
|
||||
beforePrevPostUserId={beforePrevPostUserId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ export default class PostHeader extends PureComponent {
|
|||
isGuest: PropTypes.bool,
|
||||
userTimezone: PropTypes.string,
|
||||
enableTimezone: PropTypes.bool,
|
||||
previousPostExists: PropTypes.bool,
|
||||
post: PropTypes.object,
|
||||
beforePrevPostUserId: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -58,11 +61,18 @@ export default class PostHeader extends PureComponent {
|
|||
};
|
||||
|
||||
renderCommentedOnMessage = () => {
|
||||
if (!this.props.renderReplies || !this.props.commentedOnDisplayName) {
|
||||
const {
|
||||
beforePrevPostUserId,
|
||||
commentedOnDisplayName,
|
||||
post,
|
||||
previousPostExists,
|
||||
renderReplies,
|
||||
theme,
|
||||
} = this.props;
|
||||
if (!renderReplies || !commentedOnDisplayName || (!previousPostExists && post.user_id === beforePrevPostUserId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {commentedOnDisplayName, theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
const displayName = commentedOnDisplayName;
|
||||
|
||||
|
|
|
|||
|
|
@ -260,6 +260,7 @@ export default class PostList extends PureComponent {
|
|||
// Remember that the list is rendered with item 0 at the bottom so the "previous" post
|
||||
// comes after this one in the list
|
||||
const previousPostId = index < this.props.postIds.length - 1 ? this.props.postIds[index + 1] : null;
|
||||
const beforePrevPostId = index < this.props.postIds.length - 2 ? this.props.postIds[index + 2] : null;
|
||||
const nextPostId = index > 0 ? this.props.postIds[index - 1] : null;
|
||||
|
||||
const postProps = {
|
||||
|
|
@ -274,6 +275,7 @@ export default class PostList extends PureComponent {
|
|||
onPress: this.props.onPostPress,
|
||||
renderReplies: this.props.renderReplies,
|
||||
shouldRenderReplyButton: this.props.shouldRenderReplyButton,
|
||||
beforePrevPostId,
|
||||
};
|
||||
|
||||
if (PostListUtils.isCombinedUserActivityPost(item)) {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ exports[`ChannelItem should match snapshot 1`] = `
|
|||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"maxWidth": "80%",
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
|
|
@ -202,6 +203,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"maxWidth": "80%",
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
|
|
@ -316,6 +318,7 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you
|
|||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"maxWidth": "80%",
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
|
|
@ -430,6 +433,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh
|
|||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"maxWidth": "80%",
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
|
|
@ -533,6 +537,7 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes
|
|||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"maxWidth": "80%",
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
|
|
@ -637,6 +642,7 @@ exports[`ChannelItem should match snapshot for deactivated user and not searchRe
|
|||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"maxWidth": "80%",
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
|
|
@ -745,6 +751,7 @@ exports[`ChannelItem should match snapshot with draft 1`] = `
|
|||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"maxWidth": "80%",
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
|
|
@ -851,6 +858,7 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = `
|
|||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"maxWidth": "80%",
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
paddingRight: 10,
|
||||
maxWidth: '80%',
|
||||
flex: 1,
|
||||
alignSelf: 'center',
|
||||
fontFamily: 'Open Sans',
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ exports[`channel_info should match snapshot 1`] = `
|
|||
isArchived={false}
|
||||
isBot={false}
|
||||
isGroupConstrained={false}
|
||||
isLandscape={false}
|
||||
isTeammateGuest={false}
|
||||
memberCount={2}
|
||||
onPermalinkPress={[Function]}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -603,6 +603,7 @@ export default class ChannelInfo extends PureComponent {
|
|||
isTeammateGuest={isTeammateGuest}
|
||||
hasGuests={currentChannelGuestCount > 0}
|
||||
isGroupConstrained={currentChannel.group_constrained}
|
||||
isLandscape={isLandscape}
|
||||
/>
|
||||
}
|
||||
<View style={style.rowsContainer}>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
SafeAreaView,
|
||||
} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
|
|
@ -19,6 +18,7 @@ import ChannelIcon from 'app/components/channel_icon';
|
|||
import FormattedDate from 'app/components/formatted_date';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import Markdown from 'app/components/markdown';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import BottomSheet from 'app/utils/bottom_sheet';
|
||||
import {getMarkdownTextStyles, getMarkdownBlockStyles} from 'app/utils/markdown';
|
||||
|
|
@ -44,6 +44,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
hasGuests: PropTypes.bool.isRequired,
|
||||
isGroupConstrained: PropTypes.bool,
|
||||
timeZone: PropTypes.string,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
|
|
@ -142,6 +143,7 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
isBot,
|
||||
isGroupConstrained,
|
||||
timeZone,
|
||||
isLandscape,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
|
@ -152,101 +154,99 @@ export default class ChannelInfoHeader extends React.PureComponent {
|
|||
style.detail;
|
||||
|
||||
return (
|
||||
<SafeAreaView>
|
||||
<View style={style.container}>
|
||||
<View style={[style.channelNameContainer, style.row]}>
|
||||
<ChannelIcon
|
||||
isInfo={true}
|
||||
membersCount={memberCount - 1}
|
||||
size={16}
|
||||
status={status}
|
||||
theme={theme}
|
||||
type={type}
|
||||
isArchived={isArchived}
|
||||
isBot={isBot}
|
||||
/>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={style.channelName}
|
||||
>
|
||||
{displayName}
|
||||
</Text>
|
||||
</View>
|
||||
{this.renderHasGuestText(style)}
|
||||
{purpose.length > 0 &&
|
||||
<View style={style.section}>
|
||||
<TouchableHighlight
|
||||
underlayColor={changeOpacity(theme.centerChannelColor, 0.1)}
|
||||
onLongPress={this.handlePurposeLongPress}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<FormattedText
|
||||
style={style.header}
|
||||
id='channel_info.purpose'
|
||||
defaultMessage='Purpose'
|
||||
/>
|
||||
<Markdown
|
||||
onPermalinkPress={onPermalinkPress}
|
||||
baseTextStyle={baseTextStyle}
|
||||
textStyles={textStyles}
|
||||
blockStyles={blockStyles}
|
||||
value={purpose}
|
||||
/>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
}
|
||||
{header.length > 0 &&
|
||||
<View style={style.section}>
|
||||
<TouchableHighlight
|
||||
underlayColor={changeOpacity(theme.centerChannelColor, 0.1)}
|
||||
onLongPress={this.handleHeaderLongPress}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<FormattedText
|
||||
style={style.header}
|
||||
id='channel_info.header'
|
||||
defaultMessage='Header'
|
||||
/>
|
||||
<Markdown
|
||||
onPermalinkPress={onPermalinkPress}
|
||||
baseTextStyle={baseTextStyle}
|
||||
textStyles={textStyles}
|
||||
blockStyles={blockStyles}
|
||||
value={header}
|
||||
onChannelLinkPress={popToRoot}
|
||||
/>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
}
|
||||
{isGroupConstrained &&
|
||||
<Text style={[style.createdBy, style.row]}>
|
||||
<FormattedText
|
||||
id='mobile.routes.channelInfo.groupManaged'
|
||||
defaultMessage='Members are managed by linked groups'
|
||||
/>
|
||||
</Text>
|
||||
}
|
||||
{creator &&
|
||||
<Text style={[style.createdBy, style.row]}>
|
||||
<FormattedText
|
||||
id='mobile.routes.channelInfo.createdBy'
|
||||
defaultMessage='Created by {creator} on '
|
||||
values={{
|
||||
creator,
|
||||
}}
|
||||
/>
|
||||
<FormattedDate
|
||||
format='LL'
|
||||
timeZone={timeZone}
|
||||
value={createAt}
|
||||
/>
|
||||
</Text>
|
||||
}
|
||||
<View style={style.container}>
|
||||
<View style={[style.channelNameContainer, style.row, padding(isLandscape)]}>
|
||||
<ChannelIcon
|
||||
isInfo={true}
|
||||
membersCount={memberCount - 1}
|
||||
size={16}
|
||||
status={status}
|
||||
theme={theme}
|
||||
type={type}
|
||||
isArchived={isArchived}
|
||||
isBot={isBot}
|
||||
/>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
style={style.channelName}
|
||||
>
|
||||
{displayName}
|
||||
</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
{this.renderHasGuestText(style)}
|
||||
{purpose.length > 0 &&
|
||||
<View style={[style.section, padding(isLandscape, -15)]}>
|
||||
<TouchableHighlight
|
||||
underlayColor={changeOpacity(theme.centerChannelColor, 0.1)}
|
||||
onLongPress={this.handlePurposeLongPress}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<FormattedText
|
||||
style={style.header}
|
||||
id='channel_info.purpose'
|
||||
defaultMessage='Purpose'
|
||||
/>
|
||||
<Markdown
|
||||
onPermalinkPress={onPermalinkPress}
|
||||
baseTextStyle={baseTextStyle}
|
||||
textStyles={textStyles}
|
||||
blockStyles={blockStyles}
|
||||
value={purpose}
|
||||
/>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
}
|
||||
{header.length > 0 &&
|
||||
<View style={[style.section, padding(isLandscape, -15)]}>
|
||||
<TouchableHighlight
|
||||
underlayColor={changeOpacity(theme.centerChannelColor, 0.1)}
|
||||
onLongPress={this.handleHeaderLongPress}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<FormattedText
|
||||
style={style.header}
|
||||
id='channel_info.header'
|
||||
defaultMessage='Header'
|
||||
/>
|
||||
<Markdown
|
||||
onPermalinkPress={onPermalinkPress}
|
||||
baseTextStyle={baseTextStyle}
|
||||
textStyles={textStyles}
|
||||
blockStyles={blockStyles}
|
||||
value={header}
|
||||
onChannelLinkPress={popToRoot}
|
||||
/>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
}
|
||||
{isGroupConstrained &&
|
||||
<Text style={[style.createdBy, style.row, padding(isLandscape)]}>
|
||||
<FormattedText
|
||||
id='mobile.routes.channelInfo.groupManaged'
|
||||
defaultMessage='Members are managed by linked groups'
|
||||
/>
|
||||
</Text>
|
||||
}
|
||||
{creator &&
|
||||
<Text style={[style.createdBy, style.row, padding(isLandscape)]}>
|
||||
<FormattedText
|
||||
id='mobile.routes.channelInfo.createdBy'
|
||||
defaultMessage='Created by {creator} on '
|
||||
values={{
|
||||
creator,
|
||||
}}
|
||||
/>
|
||||
<FormattedDate
|
||||
format='LL'
|
||||
timeZone={timeZone}
|
||||
value={createAt}
|
||||
/>
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ describe('channel_info_header', () => {
|
|||
isTeammateGuest: false,
|
||||
hasGuests: false,
|
||||
isGroupConstrained: false,
|
||||
isLandscape: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', async () => {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
footer: {
|
||||
marginTop: 10,
|
||||
marginHorizontal: 15,
|
||||
fontSize: 12,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {connect} from 'react-redux';
|
|||
|
||||
import {handleSuccessfulLogin, scheduleExpiredNotification} from 'app/actions/views/login';
|
||||
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
import {setStoreFromLocalData} from 'mattermost-redux/actions/general';
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ function mapStateToProps(state) {
|
|||
return {
|
||||
...state.views.selectServer,
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {Client4} from 'mattermost-redux/client';
|
|||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import Loading from 'app/components/loading';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import StatusBar from 'app/components/status_bar';
|
||||
import {resetToChannel} from 'app/actions/navigation';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
|
@ -70,6 +71,7 @@ class SSO extends PureComponent {
|
|||
handleSuccessfulLogin: PropTypes.func.isRequired,
|
||||
setStoreFromLocalData: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
useWebkit = true;
|
||||
|
|
@ -203,7 +205,7 @@ class SSO extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {theme} = this.props;
|
||||
const {theme, isLandscape} = this.props;
|
||||
const {error, messagingEnabled, renderWebView, jsCode} = this.state;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
|
|
@ -236,7 +238,7 @@ class SSO extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<View style={[style.container, padding(isLandscape)]}>
|
||||
<StatusBar/>
|
||||
{content}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export function emptyFunction() { // eslint-disable-line no-empty-function
|
|||
|
||||
export function hapticFeedback(method = 'impactLight') {
|
||||
ReactNativeHapticFeedback.trigger(method, {
|
||||
enableVibrateFallback: true,
|
||||
enableVibrateFallback: false,
|
||||
ignoreAndroidSystemSettings: false,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -454,6 +454,8 @@
|
|||
"mobile.share_extension.error_message": "An error has occurred while using the share extension.",
|
||||
"mobile.share_extension.error_title": "Extension Error",
|
||||
"mobile.share_extension.team": "Team",
|
||||
"mobile.share_extension.too_long_message": "Character count: {count}/{max}",
|
||||
"mobile.share_extension.too_long_title": "Message is too long",
|
||||
"mobile.sidebar_settings.permanent": "Permanent Sidebar",
|
||||
"mobile.sidebar_settings.permanent_description": "Keep the sidebar open permanently",
|
||||
"mobile.storage_permission_denied_description": "Upload files to your Mattermost instance. Open Settings to grant Mattermost Read and Write access to files on this device.",
|
||||
|
|
|
|||
|
|
@ -2856,7 +2856,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 238;
|
||||
CURRENT_PROJECT_VERSION = 239;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
|
@ -2917,7 +2917,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 238;
|
||||
CURRENT_PROJECT_VERSION = 239;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>238</string>
|
||||
<string>239</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.24.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>238</string>
|
||||
<string>239</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@
|
|||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>238</string>
|
||||
<string>239</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.24.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>238</string>
|
||||
<string>239</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
|
|
|||
30
patches/react-native-image-picker+0.28.1.patch
Normal file
30
patches/react-native-image-picker+0.28.1.patch
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
diff --git a/node_modules/react-native-image-picker/ios/ImagePickerManager.m b/node_modules/react-native-image-picker/ios/ImagePickerManager.m
|
||||
index 28d5870..3f70983 100644
|
||||
--- a/node_modules/react-native-image-picker/ios/ImagePickerManager.m
|
||||
+++ b/node_modules/react-native-image-picker/ios/ImagePickerManager.m
|
||||
@@ -455,12 +455,19 @@ - (void)imagePickerController:(UIImagePickerController *)picker didFinishPicking
|
||||
}
|
||||
|
||||
if (videoURL) { // Protect against reported crash
|
||||
- NSError *error = nil;
|
||||
- [fileManager moveItemAtURL:videoURL toURL:videoDestinationURL error:&error];
|
||||
- if (error) {
|
||||
- self.callback(@[@{@"error": error.localizedFailureReason}]);
|
||||
- return;
|
||||
- }
|
||||
+ NSError *error = nil;
|
||||
+
|
||||
+ // If we have write access to the source file, move it. Otherwise use copy.
|
||||
+ if ([fileManager isWritableFileAtPath:[videoURL path]]) {
|
||||
+ [fileManager moveItemAtURL:videoURL toURL:videoDestinationURL error:&error];
|
||||
+ } else {
|
||||
+ [fileManager copyItemAtURL:videoURL toURL:videoDestinationURL error:&error];
|
||||
+ }
|
||||
+
|
||||
+ if (error) {
|
||||
+ self.callback(@[@{@"error": error.localizedFailureReason}]);
|
||||
+ return;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -406,17 +406,34 @@ export default class ExtensionPost extends PureComponent {
|
|||
onPost = () => {
|
||||
const {channelId, files, value} = this.state;
|
||||
const {currentUserId} = this.props;
|
||||
const {formatMessage} = this.context.intl;
|
||||
|
||||
const data = {
|
||||
channelId,
|
||||
currentUserId,
|
||||
files,
|
||||
token: this.token,
|
||||
url: this.url,
|
||||
value,
|
||||
};
|
||||
if (value.length > MAX_MESSAGE_LENGTH) {
|
||||
Alert.alert(
|
||||
formatMessage({
|
||||
id: 'mobile.share_extension.too_long_title',
|
||||
defaultMessage: 'Message is too long',
|
||||
}),
|
||||
formatMessage({
|
||||
id: 'mobile.share_extension.too_long_message',
|
||||
defaultMessage: 'Character count: {count}/{max}',
|
||||
}, {
|
||||
count: value.length,
|
||||
max: MAX_MESSAGE_LENGTH,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const data = {
|
||||
channelId,
|
||||
currentUserId,
|
||||
files,
|
||||
token: this.token,
|
||||
url: this.url,
|
||||
value,
|
||||
};
|
||||
|
||||
this.onClose(data);
|
||||
this.onClose(data);
|
||||
}
|
||||
};
|
||||
|
||||
renderBody = () => {
|
||||
|
|
@ -435,7 +452,6 @@ export default class ExtensionPost extends PureComponent {
|
|||
<TextInput
|
||||
ref={this.getInputRef}
|
||||
autoCapitalize='sentences'
|
||||
maxLength={MAX_MESSAGE_LENGTH}
|
||||
multiline={true}
|
||||
onBlur={this.handleBlur}
|
||||
onChangeText={this.handleTextChange}
|
||||
|
|
@ -566,6 +582,29 @@ export default class ExtensionPost extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
lengthCounterColor = (count) => {
|
||||
if (count < 0) {
|
||||
return styles.textTooLong;
|
||||
}
|
||||
return styles.textLengthOk;
|
||||
}
|
||||
|
||||
renderMessageLengthRemaining = () => {
|
||||
const {value} = this.state;
|
||||
const messageLengthRemaining = MAX_MESSAGE_LENGTH - value.length;
|
||||
|
||||
if (value.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderStyle = [styles.messageLengthRemaining, this.lengthCounterColor(messageLengthRemaining)];
|
||||
return (
|
||||
<Text style={renderStyle}>
|
||||
{messageLengthRemaining}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {maxFileSize} = this.props;
|
||||
|
|
@ -610,6 +649,7 @@ export default class ExtensionPost extends PureComponent {
|
|||
<View style={styles.wrapper}>
|
||||
{this.renderBody()}
|
||||
<View style={styles.flex}>
|
||||
{this.renderMessageLengthRemaining()}
|
||||
{this.renderTeamButton()}
|
||||
{this.renderChannelButton()}
|
||||
</View>
|
||||
|
|
@ -654,6 +694,19 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
flex: 1,
|
||||
padding: 15,
|
||||
},
|
||||
messageLengthRemaining: {
|
||||
paddingTop: 5,
|
||||
paddingBottom: 5,
|
||||
paddingLeft: 15,
|
||||
paddingRight: 15,
|
||||
opacity: 0.5,
|
||||
},
|
||||
textLengthOk: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
textTooLong: {
|
||||
color: theme.errorTextColor,
|
||||
},
|
||||
input: {
|
||||
flex: 1,
|
||||
color: theme.centerChannelColor,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Alert, PermissionsAndroid} from 'react-native';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import ExtensionPost from './extension_post';
|
||||
|
||||
jest.spyOn(Alert, 'alert').mockReturnValue(true);
|
||||
jest.spyOn(PermissionsAndroid, 'check').mockReturnValue(PermissionsAndroid.RESULTS.GRANTED);
|
||||
|
||||
jest.mock('react-navigation-stack/dist/views/TouchableItem', () => {});
|
||||
|
||||
jest.mock('NativeModules', () => ({
|
||||
MattermostShare: {
|
||||
close: jest.fn(),
|
||||
},
|
||||
RNKeychainManager: {
|
||||
SECURITY_LEVEL_ANY: 'ANY',
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('app/mattermost_managed', () => ({
|
||||
getConfig: jest.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
const MAX_MESSAGE_LENGTH = 4000;
|
||||
|
||||
describe('ExtensionPost', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
getTeamChannels: jest.fn(),
|
||||
},
|
||||
channelId: 'channel-id',
|
||||
channels: {},
|
||||
currentUserId: 'current-user-id',
|
||||
maxFileSize: 1024,
|
||||
navigation: {
|
||||
setParams: jest.fn(),
|
||||
},
|
||||
teamId: 'team-id',
|
||||
};
|
||||
|
||||
const wrapper = shallowWithIntl(
|
||||
<ExtensionPost {...baseProps}/>
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
|
||||
const postMessage = (message) => {
|
||||
wrapper.setState({value: message});
|
||||
instance.onPost();
|
||||
};
|
||||
|
||||
test('should show Alert dialog if shared message is longer than maximum length permitted', () => {
|
||||
const longMessage = 'a'.repeat(MAX_MESSAGE_LENGTH + 1);
|
||||
postMessage(longMessage);
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should not show Alert dialog if shared message is within maximum length permitted', () => {
|
||||
const message = 'a'.repeat(MAX_MESSAGE_LENGTH - 1);
|
||||
postMessage(message);
|
||||
|
||||
expect(Alert.alert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not show Alert dialog if shared message is at maximum length permitted', () => {
|
||||
const exactLengthMessage = 'a'.repeat(MAX_MESSAGE_LENGTH);
|
||||
postMessage(exactLengthMessage);
|
||||
|
||||
expect(Alert.alert).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue