Unverified Commit 6f6de40a authored by Gerzon Z's avatar Gerzon Z Committed by GitHub

Merge branch 'develop' into fix.wrong-styling-e2e-encryption-banner

parents 9fd7050e 03302198
......@@ -144,7 +144,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "4.13.1"
versionName "4.14.0"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
......
......@@ -14,7 +14,6 @@ import com.facebook.react.ReactFragmentActivity;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import com.zoontek.rnbootsplash.RNBootSplash;
import com.tencent.mmkv.MMKV;
import com.google.gson.Gson;
class ThemePreferences {
......@@ -36,61 +35,6 @@ public class MainActivity extends ReactFragmentActivity {
// https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067
super.onCreate(null);
RNBootSplash.init(R.drawable.launch_screen, MainActivity.this);
MMKV.initialize(MainActivity.this);
// Start the MMKV container
MMKV defaultMMKV = MMKV.defaultMMKV();
boolean alreadyMigrated = defaultMMKV.decodeBool("alreadyMigrated");
if (!alreadyMigrated) {
// MMKV Instance that will be used by JS
MMKV mmkv = MMKV.mmkvWithID("default");
// SharedPreferences -> MMKV (Migration)
SharedPreferences sharedPreferences = getSharedPreferences("react-native", Context.MODE_PRIVATE);
mmkv.importFromSharedPreferences(sharedPreferences);
// SharedPreferences only save strings, so we saved this value as a String and now we'll need to cast into a MMKV object
// Theme preferences object
String THEME_PREFERENCES_KEY = "RC_THEME_PREFERENCES_KEY";
String themeJson = sharedPreferences.getString(THEME_PREFERENCES_KEY, "");
if (!themeJson.isEmpty()) {
ThemePreferences themePreferences = new Gson().fromJson(themeJson, ThemePreferences.class);
WritableMap themeMap = new Arguments().createMap();
themeMap.putString("currentTheme", themePreferences.currentTheme);
themeMap.putString("darkLevel", themePreferences.darkLevel);
Bundle bundle = Arguments.toBundle(themeMap);
mmkv.encode(THEME_PREFERENCES_KEY, bundle);
}
// Sort preferences object
String SORT_PREFS_KEY = "RC_SORT_PREFS_KEY";
String sortJson = sharedPreferences.getString(SORT_PREFS_KEY, "");
if (!sortJson.isEmpty()) {
SortPreferences sortPreferences = new Gson().fromJson(sortJson, SortPreferences.class);
WritableMap sortMap = new Arguments().createMap();
sortMap.putString("sortBy", sortPreferences.sortBy);
if (sortPreferences.groupByType != null) {
sortMap.putBoolean("groupByType", sortPreferences.groupByType);
}
if (sortPreferences.showFavorites != null) {
sortMap.putBoolean("showFavorites", sortPreferences.showFavorites);
}
if (sortPreferences.showUnread != null) {
sortMap.putBoolean("showUnread", sortPreferences.showUnread);
}
Bundle bundle = Arguments.toBundle(sortMap);
mmkv.encode(SORT_PREFS_KEY, bundle);
}
// Remove all our keys of SharedPreferences
sharedPreferences.edit().clear().commit();
// Mark migration complete
defaultMMKV.encode("alreadyMigrated", true);
}
}
/**
......
import React from 'react';
import PropTypes from 'prop-types';
import { View, Text, StyleSheet } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../lib/Icons';
import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles';
import { withTheme } from '../theme';
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
detailsContainer: {
flex: 1,
flexDirection: 'row'
},
detailContainer: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 8
},
detailText: {
fontSize: 10,
marginLeft: 2,
...sharedStyles.textSemibold
},
badgeContainer: {
flexDirection: 'row',
alignItems: 'center'
},
badge: {
width: 8,
height: 8,
borderRadius: 4,
marginRight: 8
}
});
const ThreadDetails = ({
item,
user,
badgeColor,
toggleFollowThread,
style,
theme
}) => {
let { tcount } = item;
if (tcount >= 1000) {
tcount = '+999';
} else if (tcount >= 100) {
tcount = '+99';
}
let replies = item?.replies?.length ?? 0;
if (replies >= 1000) {
replies = '+999';
} else if (replies >= 100) {
replies = '+99';
}
const isFollowing = item.replies?.find(u => u === user?.id);
return (
<View style={[styles.container, style]}>
<View style={styles.detailsContainer}>
<View style={styles.detailContainer}>
<CustomIcon name='threads' size={24} color={themes[theme].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{tcount}</Text>
</View>
<View style={styles.detailContainer}>
<CustomIcon name='user' size={24} color={themes[theme].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{replies}</Text>
</View>
</View>
<View style={styles.badgeContainer}>
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null }
<Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}>
<CustomIcon
size={24}
name={isFollowing ? 'notification' : 'notification-disabled'}
color={themes[theme].auxiliaryTintColor}
/>
</Touchable>
</View>
</View>
);
};
ThreadDetails.propTypes = {
item: PropTypes.object,
user: PropTypes.object,
badgeColor: PropTypes.string,
toggleFollowThread: PropTypes.func,
style: PropTypes.object,
theme: PropTypes.string
};
export default withTheme(ThreadDetails);
import React, { useContext } from 'react';
import { View, Text } from 'react-native';
import PropTypes from 'prop-types';
import Touchable from 'react-native-platform-touchable';
import { formatMessageCount } from './utils';
import styles from './styles';
import { CustomIcon } from '../../lib/Icons';
import { THREAD } from './constants';
import { themes } from '../../constants/colors';
import { formatDateThreads } from '../../utils/room';
import MessageContext from './Context';
import ThreadDetails from '../ThreadDetails';
import I18n from '../../i18n';
const Thread = React.memo(({
msg, tcount, tlm, isThreadRoom, theme, id
......@@ -21,28 +18,26 @@ const Thread = React.memo(({
const {
threadBadgeColor, toggleFollowThread, user, replies
} = useContext(MessageContext);
const time = formatDateThreads(tlm);
const buttonText = formatMessageCount(tcount, THREAD);
const isFollowing = replies?.find(u => u === user.id);
return (
<View style={styles.buttonContainer}>
<View
style={[styles.button, { backgroundColor: themes[theme].tintColor }]}
testID={`message-thread-button-${ msg }`}
>
<CustomIcon name='threads' size={16} style={[styles.buttonIcon, { color: themes[theme].buttonText }]} />
<Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{buttonText}</Text>
<Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Reply')}</Text>
</View>
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
{threadBadgeColor ? <View style={[styles.threadBadge, { backgroundColor: threadBadgeColor }]} /> : null}
<Touchable onPress={() => toggleFollowThread(isFollowing, id)}>
<CustomIcon
name={isFollowing ? 'notification' : 'notification-disabled'}
size={24}
color={themes[theme].auxiliaryText}
style={styles.threadBell}
/>
</Touchable>
<ThreadDetails
item={{
tcount,
replies,
tlm,
id
}}
user={user}
badgeColor={threadBadgeColor}
toggleFollowThread={toggleFollowThread}
style={styles.threadDetails}
/>
</View>
);
}, (prevProps, nextProps) => {
......
......@@ -176,5 +176,9 @@ export default StyleSheet.create({
},
encrypted: {
justifyContent: 'center'
},
threadDetails: {
flex: 1,
marginLeft: 12
}
});
......@@ -2,7 +2,6 @@ export const E2E_MESSAGE_TYPE = 'e2e';
export const E2E_PUBLIC_KEY = 'RC_E2E_PUBLIC_KEY';
export const E2E_PRIVATE_KEY = 'RC_E2E_PRIVATE_KEY';
export const E2E_RANDOM_PASSWORD_KEY = 'RC_E2E_RANDOM_PASSWORD_KEY';
export const E2E_REFRESH_MESSAGES_KEY = 'E2E_REFRESH_MESSAGES_KEY';
export const E2E_STATUS = {
PENDING: 'pending',
DONE: 'done'
......
import MMKVStorage from 'react-native-mmkv-storage';
import log from '../utils/log';
const MMKV = new MMKVStorage.Loader()
// MODES.MULTI_PROCESS = ACCESSIBLE BY APP GROUP (iOS)
.setProcessingMode(MMKVStorage.MODES.MULTI_PROCESS)
......@@ -11,25 +9,6 @@ const MMKV = new MMKVStorage.Loader()
class UserPreferences {
constructor() {
this.mmkv = MMKV;
this.encryptMigratedData();
}
// It should run only once
async encryptMigratedData() {
try {
const encryptMigration = await this.getBoolAsync('encryptMigration');
if (!encryptMigration) {
// Encrypt the migrated data
await this.mmkv.encryption.encrypt();
// Mark as completed
await this.setBoolAsync('encryptMigration', true);
}
} catch (e) {
log(e);
}
}
async getStringAsync(key) {
......
......@@ -31,7 +31,6 @@ import UserPreferences from '../lib/userPreferences';
import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry';
import { isOmnichannelStatusAvailable } from '../ee/omnichannel/lib';
import { E2E_REFRESH_MESSAGES_KEY } from '../lib/encryption/constants';
import Navigation from '../lib/Navigation';
const getServer = state => state.server.server;
......@@ -120,32 +119,7 @@ const fetchEnterpriseModules = function* fetchEnterpriseModules({ user }) {
}
};
const fetchRooms = function* fetchRooms({ server }) {
try {
// Read the flag to check if refresh was already done
const refreshed = yield UserPreferences.getBoolAsync(E2E_REFRESH_MESSAGES_KEY);
if (!refreshed) {
const serversDB = database.servers;
const serversCollection = serversDB.collections.get('servers');
const serverRecord = yield serversCollection.find(server);
// We need to reset roomsUpdatedAt to request all rooms again
// and save their respective E2EKeys to decrypt all pending messages and lastMessage
// that are already inserted on local database by other app version
yield serversDB.action(async() => {
await serverRecord.update((s) => {
s.roomsUpdatedAt = null;
});
});
// Set the flag to indicate that already refreshed
yield UserPreferences.setBoolAsync(E2E_REFRESH_MESSAGES_KEY, true);
}
} catch (e) {
log(e);
}
const fetchRooms = function* fetchRooms() {
yield put(roomsRequest());
};
......@@ -157,7 +131,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
RocketChat.getUserPresence(user.id);
const server = yield select(getServer);
yield fork(fetchRooms, { server });
yield fork(fetchRooms);
yield fork(fetchPermissions);
yield fork(fetchCustomEmojis);
yield fork(fetchRoles);
......
import React from 'react';
import PropTypes from 'prop-types';
import { View, Text, StyleSheet } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import { withTheme } from '../../theme';
import Avatar from '../../containers/Avatar';
import Touch from '../../utils/touch';
import sharedStyles from '../Styles';
import { themes } from '../../constants/colors';
import Markdown from '../../containers/markdown';
import { CustomIcon } from '../../lib/Icons';
import { formatDateThreads, makeThreadName } from '../../utils/room';
import ThreadDetails from '../../containers/ThreadDetails';
const styles = StyleSheet.create({
container: {
......@@ -38,34 +38,26 @@ const styles = StyleSheet.create({
avatar: {
marginRight: 8
},
detailsContainer: {
marginTop: 8,
flexDirection: 'row'
},
detailContainer: {
marginRight: 8,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center'
threadDetails: {
marginTop: 8
},
detailText: {
fontSize: 10,
marginLeft: 2,
...sharedStyles.textSemibold
badge: {
width: 8,
height: 8,
borderRadius: 4,
marginHorizontal: 8,
alignSelf: 'center'
},
badgeContainer: {
marginLeft: 8,
justifyContent: 'center'
messageContainer: {
flexDirection: 'row'
},
badge: {
width: 12,
height: 12,
borderRadius: 6
markdown: {
flex: 1
}
});
const Item = ({
item, baseUrl, theme, useRealName, user, badgeColor, onPress
item, baseUrl, theme, useRealName, user, badgeColor, onPress, toggleFollowThread
}) => {
const username = (useRealName && item?.u?.name) || item?.u?.username;
let time;
......@@ -73,13 +65,8 @@ const Item = ({
time = formatDateThreads(item.ts);
}
let tlm;
if (item?.tlm) {
tlm = formatDateThreads(item.tlm);
}
return (
<Touch theme={theme} onPress={() => onPress(item)} testID={`thread-messages-view-${ item.msg }`} style={{ backgroundColor: themes[theme].backgroundColor }}>
<Touchable onPress={() => onPress(item)} testID={`thread-messages-view-${ item.msg }`} style={{ backgroundColor: themes[theme].backgroundColor }}>
<View style={styles.container}>
<Avatar
style={styles.avatar}
......@@ -96,33 +83,19 @@ const Item = ({
<Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}>{username}</Text>
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
</View>
<Markdown msg={makeThreadName(item)} baseUrl={baseUrl} username={username} theme={theme} numberOfLines={2} preview />
<View style={styles.detailsContainer}>
<View style={styles.detailContainer}>
<CustomIcon name='threads' size={20} color={themes[theme].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]}>{item?.tcount}</Text>
</View>
<View style={styles.detailContainer}>
<CustomIcon name='user' size={20} color={themes[theme].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]}>{item?.replies?.length}</Text>
</View>
<View style={styles.detailContainer}>
<CustomIcon name='clock' size={20} color={themes[theme].auxiliaryText} />
<Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]}>{tlm}</Text>
</View>
<View style={styles.messageContainer}>
<Markdown msg={makeThreadName(item)} baseUrl={baseUrl} username={username} theme={theme} numberOfLines={2} style={[styles.markdown]} preview />
{badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null }
</View>
<ThreadDetails
item={item}
user={user}
toggleFollowThread={toggleFollowThread}
style={styles.threadDetails}
/>
</View>
{badgeColor
? (
<View style={styles.badgeContainer}>
<View style={[styles.badge, { backgroundColor: badgeColor }]} />
</View>
)
: null}
</View>
</Touch>
</Touchable>
);
};
......@@ -133,7 +106,8 @@ Item.propTypes = {
useRealName: PropTypes.bool,
user: PropTypes.object,
badgeColor: PropTypes.string,
onPress: PropTypes.func
onPress: PropTypes.func,
toggleFollowThread: PropTypes.func
};
export default withTheme(Item);
......@@ -33,6 +33,8 @@ import { isIOS } from '../../utils/deviceInfo';
import { getBadgeColor, makeThreadName } from '../../utils/room';
import { getHeaderTitlePosition } from '../../containers/Header';
import SearchHeader from './SearchHeader';
import EventEmitter from '../../utils/events';
import { LISTENER } from '../../containers/Toast';
const API_FETCH_COUNT = 50;
......@@ -410,6 +412,15 @@ class ThreadMessagesView extends React.Component {
this.setState({ currentFilter: filter, displayingThreads });
}
toggleFollowThread = async(isFollowingThread, tmid) => {
try {
await RocketChat.toggleFollowMessage(tmid, !isFollowingThread);
EventEmitter.emit(LISTENER, { message: isFollowingThread ? I18n.t('Unfollowed_thread') : I18n.t('Following_thread') });
} catch (e) {
log(e);
}
}
renderItem = ({ item }) => {
const {
user, navigation, baseUrl, useRealName
......@@ -426,6 +437,7 @@ class ThreadMessagesView extends React.Component {
badgeColor
}}
onPress={this.onThreadPress}
toggleFollowThread={this.toggleFollowThread}
/>
);
}
......
......@@ -1717,7 +1717,7 @@
INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 4.13.1;
MARKETING_VERSION = 4.14.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService;
......@@ -1754,7 +1754,7 @@
INFOPLIST_FILE = NotificationService/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
MARKETING_VERSION = 4.13.1;
MARKETING_VERSION = 4.14.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService;
PRODUCT_NAME = "$(TARGET_NAME)";
......
......@@ -69,27 +69,6 @@ static void InitializeFlipper(UIApplication *application) {
// AppGroup MMKV
NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]].path;
[MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogNone];
// Start the MMKV container
MMKV *defaultMMKV = [MMKV mmkvWithID:@"migration" mode:MMKVMultiProcess];
BOOL alreadyMigrated = [defaultMMKV getBoolForKey:@"alreadyMigrated"];
if (!alreadyMigrated) {
// MMKV Instance that will be used by JS
MMKV *mmkv = [MMKV mmkvWithID:@"default" mode:MMKVMultiProcess];
// NSUserDefaults -> MMKV (Migration)
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]];
[mmkv migrateFromUserDefaults:userDefaults];
// Remove our own keys of NSUserDefaults
for (NSString *key in [userDefaults dictionaryRepresentation].keyEnumerator) {
[userDefaults removeObjectForKey:key];
}
// Mark migration complete
[defaultMMKV setBool:YES forKey:@"alreadyMigrated"];
}
return YES;
}
......
......@@ -23,7 +23,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.13.1</string>
<string>4.14.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
......
......@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>4.13.1</string>
<string>4.14.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>KeychainGroup</key>
......
......@@ -469,11 +469,6 @@ export default ({ theme }) => {
tcount={1}
tlm={date}
/>
<Message
msg='How are you?'
tcount={9999}
tlm={date}
/>
<Message
msg="I'm fine!"
tmid='1'
......