* refactor: implement custom ExpoImage wrapper for cache control Add ExpoImage component with automatic cacheKey/cachePath management and replace all expo-image imports across the app * refactor(ios): convert Gekidou to CocoaPods Migrate from Swift Package Manager to CocoaPods, add Keychain write operations, refactor notification handler to remove react-native-notifications headers, and upgrade Swift to 5.0 * npm audit * update fastlane * feat(ci): integrate Intune MAM for enterprise builds with strict OSS protection Add Intune submodule, CI actions, Fastlane configuration, developer scripts, pre-commit hooks, and validation workflows to enable internal MAM builds while protecting OSS repository * fix tests by mocking @mattermost/intune * feat: implement Intune MAM integration with comprehensive security enforcement Add IntuneManager, refactor SecurityManager/SessionManager for MAM policies, implement native OIDC auth flow, add biometric enforcement, conditional launch blocking, and file protection controls * fix alerts when no server database is present * Unify cache strategy * fix emit config changed after it was stored in the db * Handle Mid-Session Enrollment Detection * fix ADALLogOverrideDisabled missing in Fastfile * fix flow for initial enrollment * fix and add unit tests * enable Intune configuration for PR and beta builds, CLIENT_ID should be changed before actual release * Update intune submodule with addressed feedback * fix validate-intune-clean workflow * feat(intune): add comprehensive error handling and SAML+Entra support Add production-ready error handling for native Entra authentication with user-friendly i18n messages, comprehensive test coverage, and support for Entra login when server requires SAML. * update i18n * update intune submodule * update build-pr token * fix race condition between server auth and intune enrollment * fix CI workflow to build with intune * use deploy key for intune submodule * set the config directly in the submodule .git * debug injection * try setting GIT_SSH_COMMAND * remove action debug * fix server url input * match pod cache with intune hash * Fastfile and envs * have workflows check for intune/.git * have ci cache intune frameworks as well * update Fastlane to set no-cache to artifacts uploaded * fix s3 upload * fix pblist template * Attempt to remove the cache control for PR uploads to s3 * use hash from commit for S3 path * Implement crash-resilient selective wipe with automatic retry and add removeInternetPassword to Gekidou Keychain * Fix surface errors from intune login * fix postinstall scripts * use cacheKey for draft md images * remove unnecessary double await during test * Have isMinimumLicenseTier accept valid license sku tier as target * Add missing Auth error messages * remove the last period for intune errors in i18n * do not call unenroll with wipe on manual logout * Fix tests and Intune error messages * do not filter any SSO type regardless of which is used for Intune * fix 412 to not retry * fix tests, app logs sharing and share_extension avatar cache * apply setScreenCapturePolicy on license change Co-authored-by: Eva Sarafianou <eva.sarafianou@mattermost.com> * re-apply screen capture on enrollment Co-authored-by: Eva Sarafianou <eva.sarafianou@mattermost.com> * use userData from intunr login and prevent getMe Co-authored-by: Eva Sarafianou <eva.sarafianou@mattermost.com> * Check for Biometrics and Jailbreak as we used to --------- Co-authored-by: Eva Sarafianou <eva.sarafianou@mattermost.com>
152 lines
4.6 KiB
TypeScript
152 lines
4.6 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
|
import React from 'react';
|
|
import {StyleSheet, Text} from 'react-native';
|
|
import {of as of$} from 'rxjs';
|
|
import {switchMap} from 'rxjs/operators';
|
|
|
|
import {fetchCustomEmojiInBatch} from '@actions/remote/custom_emoji';
|
|
import ExpoImage from '@components/expo_image';
|
|
import {useServerUrl} from '@context/server';
|
|
import NetworkManager from '@managers/network_manager';
|
|
import {queryCustomEmojisByName} from '@queries/servers/custom_emoji';
|
|
import {observeConfigBooleanValue} from '@queries/servers/system';
|
|
import {EmojiIndicesByAlias, Emojis} from '@utils/emoji';
|
|
import {isUnicodeEmoji} from '@utils/emoji/helpers';
|
|
|
|
import type {EmojiProps} from '@typings/components/emoji';
|
|
import type {WithDatabaseArgs} from '@typings/database/database';
|
|
|
|
const assetImages = new Map([['mattermost.png', require('@assets/images/emojis/mattermost.png')]]);
|
|
|
|
const Emoji = (props: EmojiProps) => {
|
|
const {
|
|
customEmojis,
|
|
imageStyle,
|
|
displayTextOnly,
|
|
emojiName,
|
|
literal = '',
|
|
testID,
|
|
textStyle,
|
|
commonStyle,
|
|
} = props;
|
|
const serverUrl = useServerUrl();
|
|
let assetImage = '';
|
|
let unicode;
|
|
let imageUrl = '';
|
|
const name = emojiName.trim();
|
|
if (EmojiIndicesByAlias.has(name)) {
|
|
const emoji = Emojis[EmojiIndicesByAlias.get(name)!];
|
|
if (emoji.category === 'custom') {
|
|
assetImage = emoji.fileName;
|
|
} else {
|
|
unicode = emoji.image;
|
|
}
|
|
} else {
|
|
const custom = customEmojis.find((ce) => ce.name === name);
|
|
if (custom) {
|
|
try {
|
|
const client = NetworkManager.getClient(serverUrl);
|
|
imageUrl = client.getCustomEmojiImageUrl(custom.id);
|
|
} catch {
|
|
// do nothing
|
|
}
|
|
} else if (name && (name.length > 1 || !isUnicodeEmoji(name))) {
|
|
fetchCustomEmojiInBatch(serverUrl, name);
|
|
}
|
|
}
|
|
|
|
let size = props.size;
|
|
let fontSize = size;
|
|
if (!size && textStyle) {
|
|
const flatten = StyleSheet.flatten(textStyle);
|
|
fontSize = flatten.fontSize;
|
|
size = fontSize;
|
|
}
|
|
|
|
if (displayTextOnly || (!imageUrl && !assetImage && !unicode)) {
|
|
return (
|
|
<Text
|
|
style={[commonStyle, textStyle]}
|
|
testID={testID}
|
|
>
|
|
{literal}
|
|
</Text>);
|
|
}
|
|
|
|
const width = size;
|
|
const height = size;
|
|
|
|
if (unicode && !imageUrl) {
|
|
const codeArray = unicode.split('-');
|
|
const code = codeArray.reduce((acc: string, c: string) => {
|
|
return acc + String.fromCodePoint(parseInt(c, 16));
|
|
}, '');
|
|
|
|
return (
|
|
<Text
|
|
style={[commonStyle, textStyle, {fontSize: size, color: '#000'}]}
|
|
testID={testID}
|
|
>
|
|
{code}
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
const key = (`${assetImage}-${height}-${width}`);
|
|
if (assetImage) {
|
|
const image = assetImages.get(assetImage);
|
|
if (!image) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<ExpoImage
|
|
id={`emoji-${emojiName}`}
|
|
source={image}
|
|
style={[commonStyle, imageStyle, {width, height}]}
|
|
contentFit='contain'
|
|
testID={testID}
|
|
recyclingKey={key}
|
|
transition={0}
|
|
cachePolicy='memory'
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (!imageUrl) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<ExpoImage
|
|
id={`emoji-${emojiName}`}
|
|
style={[commonStyle, imageStyle, {width, height}]}
|
|
source={{uri: imageUrl}}
|
|
contentFit='contain'
|
|
testID={testID}
|
|
recyclingKey={key}
|
|
cachePolicy='disk'
|
|
placeholder={require('@assets/images/thumb.png')}
|
|
placeholderContentFit='contain'
|
|
transition={0}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const withCustomEmojis = withObservables(['emojiName'], ({database, emojiName}: WithDatabaseArgs & {emojiName: string}) => {
|
|
const hasEmojiBuiltIn = EmojiIndicesByAlias.has(emojiName);
|
|
|
|
const displayTextOnly = hasEmojiBuiltIn ? of$(false) : observeConfigBooleanValue(database, 'EnableCustomEmoji').pipe(
|
|
switchMap((value) => of$(!value)),
|
|
);
|
|
|
|
return {
|
|
displayTextOnly,
|
|
customEmojis: hasEmojiBuiltIn ? of$([]) : queryCustomEmojisByName(database, [emojiName]).observe(),
|
|
};
|
|
});
|
|
|
|
export default withDatabase(withCustomEmojis(Emoji));
|