Merge 4.14.0 into single-server (#2833)
* [FIX] RoomItem using deprecated animated event signature (#2771) * [FIX] Server autocomplete text breaking line (#2774) * [FIX] ServerDropdown flashing bigger server icon (#2775) * [FIX] ServerDropdown flashing bigger server icon * Remove unused logo and update image path where needed * Minor tweak Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Rooms list not being updated on some cases (#2765) * Request subscriptions on RoomsListView.constructor * Removes opened rooms from last message persisting * Change server reducer * Prevent undefined ids causing query error * [FIX] Share Extension hitting memory limit on iOS (#2788) * [FIX] Disallow swipe to dismiss on share extension * Limit query to 20 and clean up props * Remove rn-extension-share branch pointer * Test new branch * Remove branch * [IMPROVEMENT] Threads layout tweaks (#2686) * improvement: Thread Details * fix: re-render Thread Messages Item * fix: update snapshots * improve: thread details component * fix: cast replies length * improvement: format date of threads * improvement: thread details styles * fix: wrap text * tests: update snapshot * improvement: use same date format for all dates * Icon size 24 * Remove date * Remove prop drill * Badge position * Badge container tweak * Fix inline style * Move ThreadDetails to containers * Update stories * Fix lint * Remove wrong prop Co-authored-by: Diego Mello <diegolmello@gmail.com> * [CHORE] Remove some migrations (#2792) * Remove force rooms refresh * Remove MMKV migration * Bump version to 4.14.0 (#2797) * [FIX] Messagebox tracking lost on pop gesture navigation (#2799) * Use setTimeout instead of InteractionManager * Update tracking lib * [FIX] Back button closing activity when on root stack screen (#2804) * Make hardware back button to behave as home button on root screens * Remove unnecessary code * Remove handleBackPress from OnboardingView * Fix lint * [i18n] Add missing German strings (#2715) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [NEW] Encrypted Discussions (#2813) * I18n key fix * Add encrypted switch * Remove unused i18n keys * Add enabled to encryption reducer * Show encrypted option on CreateDiscussionView only when e2e encryption is properly set * Add localSearch and use it on search * Use encrypted from parent channel * Fix method calls as rest api with 2fa enabled * Fix logout after reset keys * Use encryption reducer instead of lib directly to check render * Check for room type logic to display encryption option on create discussion * Check toggle-room-e2e-encryption permission on RoomActionsView * Check for encryption status instead of setting on server * Fix * Disable switch instead of hide it * Fix spotlight for DMs * Fix server test * [FIX] Messagebox missing style for text color (#2786) * Changing auxilaryTintColor * Changed Placeholder color to BodyText color * added color prop * eslint changes * used array for styles Co-authored-by: Diego Mello <diegolmello@gmail.com> * [I18N] Update arabic (#2696) * Update ar.js * Update ar.js Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Workspace input without i18n (#2689) * [FIX] Translation of strings in Login page * Strings are added for translation. fixes: #2620 * Add pt-BR Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Spotlight returning duplicated entries (#2805) * Update rocketchat.js * Updated search function * Minor improvements * Remove atIndex * Add remove logic to remove duplicate data from response Co-authored-by: Diego Mello <diegolmello@gmail.com> * [CHORE] Refactor ServerItem (#2778) * Updated ServerDropdown and ServerItem * Added ServerItem stories * Update ServerDropdown.js * Updated ServerItem stories * Updated ServerItem stories and ServerItem component * Updated SelectServerView, ServerItem and ServerItem stories * Updated ServerItem stories * Updated ServerItem stories * Update tests Co-authored-by: Diego Mello <diegolmello@gmail.com> * [DOCS] Updated Quick Start docs link in e2e/readme (#2802) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [I18N] Add Turkish (#2793) * Turkish language support added * Update tr.js Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Lint on #2793 (#2818) * [I18N] Add missing german strings (#2689) (#2820) * [I18N] Add missing italian strings (#2817) * [FIX] Server version becoming null on server change (#2821) * [FIX] Wrong styling on E2E encryption banner (#2767) * [FIX] Wrong styling on E2E encryption banner * [FIX] Wrong styling on E2E encryption banner * [FIX] Wrong styling on E2E encryption banner * [FIX] Wrong styling on E2E encryption banner (#2767) * Updated SortDropdown, ListHeader, ListItem and added stories for List.Item * Updated SortDropdown * Removed unused component * Updated List.Item and stories * Reverted unnecessary changes and updated ListItem stories * Fix minor indentation * Stop breaking Touch's default underlay color * Fix indentation * Remove falsy comparison from render * Fix left icon * Use List.Item on OmnichannelStatus * Add missing separator * Lint * Fix sort dropdown * Remove unnecessary styles * Fix detox Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] App Store using Experimental's app id (#2826) * [FIX] Wrong username on push notifications (#2825) Co-authored-by: Gerzon Z <gerzonzcanario@gmail.com> Co-authored-by: Gerzon Z <gerzonc@icloud.com> Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: phriedrich <info@phriedrich.de> Co-authored-by: yash-rajpal <58601732+yash-rajpal@users.noreply.github.com> Co-authored-by: Fazil Boudjelal <fazildiablou@hotmail.fr> Co-authored-by: Sumukha Hegde <SUMUKHA214@GMAIL.COM> Co-authored-by: Hakan YILMAZ <mukerrem.yilmaz@hotmail.com> Co-authored-by: Vincenzo Esposito <aenon.esposito@gmail.com>
|
@ -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]
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
|
||||
|
|
|
@ -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,11 @@ 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);
|
||||
}
|
||||
@Override
|
||||
public void invokeDefaultOnBackPressed() {
|
||||
moveTaskToBack(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 11 KiB |
|
@ -257,7 +257,6 @@ public class CustomPushNotification extends PushNotification {
|
|||
Notification.MessagingStyle messageStyle;
|
||||
|
||||
Gson gson = new Gson();
|
||||
Ejson ejson = gson.fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
messageStyle = new Notification.MessagingStyle("");
|
||||
|
@ -278,25 +277,19 @@ public class CustomPushNotification extends PushNotification {
|
|||
|
||||
long timestamp = data.getLong("time");
|
||||
String message = data.getString("message");
|
||||
String username = data.getString("username");
|
||||
String senderId = data.getString("senderId");
|
||||
String avatarUri = data.getString("avatarUri");
|
||||
|
||||
Ejson ejson = gson.fromJson(data.getString("ejson", "{}"), Ejson.class);
|
||||
String m = extractMessage(message, ejson);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
messageStyle.addMessage(m, timestamp, username);
|
||||
messageStyle.addMessage(m, timestamp, ejson.senderName);
|
||||
} else {
|
||||
Bitmap avatar = getAvatar(avatarUri);
|
||||
|
||||
String name = username;
|
||||
if (ejson.senderName != null) {
|
||||
name = ejson.senderName;
|
||||
}
|
||||
|
||||
Person.Builder sender = new Person.Builder()
|
||||
.setKey(senderId)
|
||||
.setName(name);
|
||||
.setName(ejson.senderName);
|
||||
|
||||
if (avatar != null) {
|
||||
sender.setIcon(Icon.createWithBitmap(avatar));
|
||||
|
|
|
@ -7,7 +7,7 @@ import { connect } from 'react-redux';
|
|||
import Navigation from './lib/Navigation';
|
||||
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
|
||||
import {
|
||||
ROOT_LOADING, ROOT_OUTSIDE, ROOT_NEW_SERVER, ROOT_INSIDE, ROOT_SET_USERNAME, ROOT_BACKGROUND
|
||||
ROOT_LOADING, ROOT_OUTSIDE, ROOT_NEW_SERVER, ROOT_INSIDE, ROOT_SET_USERNAME
|
||||
} from './actions/app';
|
||||
|
||||
// Stacks
|
||||
|
@ -65,7 +65,7 @@ const App = React.memo(({ root, isMasterDetail }) => {
|
|||
>
|
||||
<Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}>
|
||||
<>
|
||||
{root === ROOT_LOADING || root === ROOT_BACKGROUND ? (
|
||||
{root === ROOT_LOADING ? (
|
||||
<Stack.Screen
|
||||
name='AuthLoading'
|
||||
component={AuthLoadingView}
|
||||
|
|
|
@ -69,4 +69,4 @@ export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [
|
|||
export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']);
|
||||
export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']);
|
||||
export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']);
|
||||
export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET_BANNER']);
|
||||
export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET', 'SET_BANNER']);
|
||||
|
|
|
@ -5,7 +5,6 @@ export const ROOT_INSIDE = 'inside';
|
|||
export const ROOT_LOADING = 'loading';
|
||||
export const ROOT_NEW_SERVER = 'newServer';
|
||||
export const ROOT_SET_USERNAME = 'setUsername';
|
||||
export const ROOT_BACKGROUND = 'background';
|
||||
|
||||
export function appStart({ root, ...args }) {
|
||||
return {
|
||||
|
|
|
@ -12,6 +12,14 @@ export function encryptionStop() {
|
|||
};
|
||||
}
|
||||
|
||||
export function encryptionSet(enabled = false, banner = null) {
|
||||
return {
|
||||
type: types.ENCRYPTION.SET,
|
||||
enabled,
|
||||
banner
|
||||
};
|
||||
}
|
||||
|
||||
export function encryptionSetBanner(banner) {
|
||||
return {
|
||||
type: types.ENCRYPTION.SET_BANNER,
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { SERVER } from './actionsTypes';
|
||||
|
||||
export function selectServerRequest(server, version, fetchVersion = true) {
|
||||
export function selectServerRequest(server, version, fetchVersion = true, changeServer = false) {
|
||||
return {
|
||||
type: SERVER.SELECT_REQUEST,
|
||||
server,
|
||||
version,
|
||||
fetchVersion
|
||||
fetchVersion,
|
||||
changeServer
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ export const themes = {
|
|||
auxiliaryText: '#9ca2a8',
|
||||
infoText: '#6d6d72',
|
||||
tintColor: '#1d74f5',
|
||||
tintActive: '#549df9',
|
||||
auxiliaryTintColor: '#6C727A',
|
||||
actionTintColor: '#1d74f5',
|
||||
separatorColor: '#cbcbcc',
|
||||
|
@ -80,6 +81,7 @@ export const themes = {
|
|||
auxiliaryText: '#9297a2',
|
||||
infoText: '#6D6D72',
|
||||
tintColor: '#1d74f5',
|
||||
tintActive: '#549df9',
|
||||
auxiliaryTintColor: '#f9f9f9',
|
||||
actionTintColor: '#1d74f5',
|
||||
separatorColor: '#2b2b2d',
|
||||
|
@ -125,6 +127,7 @@ export const themes = {
|
|||
auxiliaryText: '#b2b8c6',
|
||||
infoText: '#6d6d72',
|
||||
tintColor: '#1e9bfe',
|
||||
tintActive: '#76b7fc',
|
||||
auxiliaryTintColor: '#f9f9f9',
|
||||
actionTintColor: '#1e9bfe',
|
||||
separatorColor: '#272728',
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
I18nManager
|
||||
View, Text, StyleSheet, I18nManager
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
@ -82,11 +79,12 @@ const Content = React.memo(({
|
|||
));
|
||||
|
||||
const Button = React.memo(({
|
||||
onPress, ...props
|
||||
onPress, backgroundColor, underlayColor, ...props
|
||||
}) => (
|
||||
<Touch
|
||||
onPress={() => onPress(props.title)}
|
||||
style={{ backgroundColor: themes[props.theme].backgroundColor }}
|
||||
style={{ backgroundColor: backgroundColor || themes[props.theme].backgroundColor }}
|
||||
underlayColor={underlayColor}
|
||||
enabled={!props.disabled}
|
||||
theme={props.theme}
|
||||
>
|
||||
|
@ -99,7 +97,7 @@ const ListItem = React.memo(({ ...props }) => {
|
|||
return <Button {...props} />;
|
||||
}
|
||||
return (
|
||||
<View style={{ backgroundColor: themes[props.theme].backgroundColor }}>
|
||||
<View style={{ backgroundColor: props.backgroundColor || themes[props.theme].backgroundColor }}>
|
||||
<Content {...props} />
|
||||
</View>
|
||||
);
|
||||
|
@ -107,7 +105,8 @@ const ListItem = React.memo(({ ...props }) => {
|
|||
|
||||
ListItem.propTypes = {
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
theme: PropTypes.string,
|
||||
backgroundColor: PropTypes.string
|
||||
};
|
||||
|
||||
ListItem.displayName = 'List.Item';
|
||||
|
@ -137,7 +136,9 @@ Button.propTypes = {
|
|||
title: PropTypes.string,
|
||||
onPress: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
theme: PropTypes.string
|
||||
theme: PropTypes.string,
|
||||
backgroundColor: PropTypes.string,
|
||||
underlayColor: PropTypes.string
|
||||
};
|
||||
|
||||
Button.defaultProps = {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
View, Alert, Keyboard, NativeModules, Text, InteractionManager
|
||||
View, Alert, Keyboard, NativeModules, Text
|
||||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
|
||||
|
@ -224,12 +224,12 @@ class MessageBox extends Component {
|
|||
this.unsubscribeFocus = navigation.addListener('focus', () => {
|
||||
// didFocus
|
||||
// We should wait pushed views be dismissed
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.trackingTimeout = setTimeout(() => {
|
||||
if (this.tracking && this.tracking.resetTracking) {
|
||||
// Reset messageBox keyboard tracking
|
||||
this.tracking.resetTracking();
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
this.unsubscribeBlur = navigation.addListener('blur', () => {
|
||||
this.component?.blur();
|
||||
|
@ -258,6 +258,10 @@ class MessageBox extends Component {
|
|||
} else if (!nextProps.message) {
|
||||
this.clearInput();
|
||||
}
|
||||
if (this.trackingTimeout) {
|
||||
clearTimeout(this.trackingTimeout);
|
||||
this.trackingTimeout = false;
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
|
@ -932,7 +936,7 @@ class MessageBox extends Component {
|
|||
/>
|
||||
<TextInput
|
||||
ref={component => this.component = component}
|
||||
style={styles.textBoxInput}
|
||||
style={[styles.textBoxInput, { color: themes[theme].bodyText }]}
|
||||
returnKeyType='default'
|
||||
keyboardType='twitter'
|
||||
blurOnSubmit={false}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
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);
|
|
@ -1,15 +1,12 @@
|
|||
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
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import React, { memo, useState, useEffect } from 'react';
|
||||
import {
|
||||
View, Text, StyleSheet, Switch
|
||||
} from 'react-native';
|
||||
import { View, Switch } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Touch from '../../../utils/touch';
|
||||
import { CustomIcon } from '../../../lib/Icons';
|
||||
import I18n from '../../../i18n';
|
||||
import * as List from '../../../containers/List';
|
||||
import styles from '../../../views/RoomsListView/styles';
|
||||
import { themes, SWITCH_TRACK_COLOR } from '../../../constants/colors';
|
||||
import { withTheme } from '../../../theme';
|
||||
|
@ -36,35 +32,32 @@ const OmnichannelStatus = memo(({
|
|||
};
|
||||
|
||||
return (
|
||||
<Touch
|
||||
onPress={goQueue}
|
||||
theme={theme}
|
||||
style={{ backgroundColor: themes[theme].headerSecondaryBackground }}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.dropdownContainerHeader,
|
||||
{ borderBottomWidth: StyleSheet.hairlineWidth, borderColor: themes[theme].separatorColor }
|
||||
]}
|
||||
>
|
||||
<CustomIcon style={[styles.queueIcon, { color: themes[theme].auxiliaryText }]} size={22} name='omnichannel' />
|
||||
<Text style={[styles.queueToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Omnichannel')}</Text>
|
||||
{inquiryEnabled
|
||||
? (
|
||||
<UnreadBadge
|
||||
style={styles.queueIcon}
|
||||
unread={queueSize}
|
||||
<>
|
||||
<List.Item
|
||||
title='Omnichannel'
|
||||
left={() => <List.Icon name='omnichannel' />}
|
||||
color={themes[theme].auxiliaryText}
|
||||
onPress={goQueue}
|
||||
right={() => (
|
||||
<View style={styles.omnichannelRightContainer}>
|
||||
{inquiryEnabled
|
||||
? (
|
||||
<UnreadBadge
|
||||
style={styles.queueIcon}
|
||||
unread={queueSize}
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
<Switch
|
||||
value={status}
|
||||
trackColor={SWITCH_TRACK_COLOR}
|
||||
onValueChange={toggleLivechat}
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
<Switch
|
||||
style={styles.omnichannelToggle}
|
||||
value={status}
|
||||
trackColor={SWITCH_TRACK_COLOR}
|
||||
onValueChange={toggleLivechat}
|
||||
/>
|
||||
</View>
|
||||
</Touch>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
<List.Separator />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ export const LANGUAGES = [
|
|||
label: 'العربية',
|
||||
value: 'ar',
|
||||
file: require('./locales/ar').default
|
||||
}, {
|
||||
label: 'Türkçe',
|
||||
value: 'tr',
|
||||
file: require('./locales/tr').default
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ export default {
|
|||
Apply_Your_Certificate: 'طبق شهادتك',
|
||||
Applying_a_theme_will_change_how_the_app_looks: 'سيؤدي تطبيق السمة إلى تغيير شكل التطبيق',
|
||||
ARCHIVE: 'أرشفة',
|
||||
archive: 'أرشيف',
|
||||
archive: 'أرشفة',
|
||||
are_typing: 'يكتب',
|
||||
Are_you_sure_question_mark: 'هل أنت متأكد؟',
|
||||
Are_you_sure_you_want_to_leave_the_room: 'متأكد من مغادرة الغرفة {{room}}؟',
|
||||
|
@ -129,8 +129,8 @@ export default {
|
|||
creating_invite: 'إنشاء دعوة',
|
||||
Channel_Name: 'اسم القناة',
|
||||
Channels: 'قنوات',
|
||||
Chats: 'محادثات',
|
||||
Call_already_ended: 'انتهت المكالمة بالفعل!',
|
||||
Chats: 'الرسائل',
|
||||
Call_already_ended: 'تم انهاء المكالمة بالفعل !',
|
||||
Clear_cookies_alert: 'هل تريد حذف جميع ملفات تعريف الإرتباط؟',
|
||||
Clear_cookies_desc: 'هذا الإجراء سيحذف ملفات تعريف الإرتباط الخاصة بتسجيل الدخول مما يسمح بتسجيل الدخول لحسابات أخرى',
|
||||
Clear_cookies_yes: 'نعم، مسح ملفات الإرتباط',
|
||||
|
@ -478,8 +478,8 @@ export default {
|
|||
Send_to: 'إرسال إلى...',
|
||||
Sending_to: 'يتم الإرسال إلى',
|
||||
Sent_an_attachment: 'تم إرسال المرفق',
|
||||
Server: 'خادم',
|
||||
Servers: 'خوادم',
|
||||
Server: 'سرفر',
|
||||
Servers: 'سرفرات',
|
||||
Server_version: 'نسخة الخادم: {{version}}',
|
||||
Set_username_subtitle: 'يتم استخدام اسم المستخدم للسماح للآخرين بذكرك في الرسائل',
|
||||
Set_custom_status: 'حدد حالة خاصة',
|
||||
|
@ -562,7 +562,6 @@ export default {
|
|||
Username: 'اسم المستخدم',
|
||||
Username_or_email: 'اسم المستخدم أو البريد الالكتروني',
|
||||
Uses_server_configuration: 'يستخدم إعداد الخادم',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'عادةً، النقاش يبدأ بسؤال، على سبيل المثال: كيف أرفع صورة؟',
|
||||
Validating: 'يتم التحقق',
|
||||
Registration_Succeeded: 'تم التسجيل بنجاح',
|
||||
Verify: 'تحقق',
|
||||
|
@ -597,7 +596,6 @@ export default {
|
|||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'تحتاج إلى الوصول إلى خادم Rocket.Chat واحد على الأقل لمشاركة شيء ما',
|
||||
You_need_to_verifiy_your_email_address_to_get_notications: 'يجب تأكيد البريد الإلكتروني حتى تصلك الإشعارات',
|
||||
Your_certificate: 'شهادتك',
|
||||
Your_message: 'رسالتك',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses: 'سوف تنتهي صلاحية رابط الدعوة الخاص بك بعد {{usesLeft}} استخدامات',
|
||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}} أو بعد {{usesLeft}} استخدامات',
|
||||
Your_invite_link_will_expire_on__date__: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}}',
|
||||
|
|
|
@ -280,6 +280,8 @@ export default {
|
|||
Invite_Link: 'Einladungs-Link',
|
||||
Invite_users: 'Benutzer einladen',
|
||||
Join: 'Beitreten',
|
||||
Join_Code: 'Beitrittscode',
|
||||
Insert_Join_Code: 'Beitrittscode eingeben',
|
||||
Join_our_open_workspace: 'Tritt unserem offenen Arbeitsbereich bei',
|
||||
Join_your_workspace: 'Tritt deinem Arbeitsbereich bei',
|
||||
Just_invited_people_can_access_this_channel: 'Nur eingeladene Personen können auf diesen Kanal zugreifen',
|
||||
|
@ -318,7 +320,7 @@ export default {
|
|||
Message_Reported: 'Nachricht gemeldet',
|
||||
Microphone_Permission_Message: 'Rocket.Chat benötigt Zugriff auf das Mikrofon, damit du eine Audionachricht senden kannst.',
|
||||
Microphone_Permission: 'Mikrofonberechtigung',
|
||||
Mute: 'Stumm',
|
||||
Mute: 'Diesem Benutzer das Chatten verbieten',
|
||||
muted: 'stummgeschaltet',
|
||||
My_servers: 'Meine Server',
|
||||
N_people_reacted: '{{n}} Leute haben reagiert',
|
||||
|
@ -563,7 +565,6 @@ export default {
|
|||
Username: 'Benutzername',
|
||||
Username_or_email: 'Benutzername oder E-Mail-Adresse',
|
||||
Uses_server_configuration: 'Nutzt Servereinstellungen',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Üblicherweise beginnt eine Diskussion mit einer Frage, beispielsweise: "Wie lade ich ein Bild hoch?"',
|
||||
Validating: 'Validierung',
|
||||
Registration_Succeeded: 'Registrierung erfolgreich!',
|
||||
Verify: 'Überprüfen',
|
||||
|
@ -598,7 +599,6 @@ export default {
|
|||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Du benötigst Zugang zu mindestens einem Rocket.Chat-Server um etwas zu teilen.',
|
||||
You_need_to_verifiy_your_email_address_to_get_notications: 'Du musst deine Email-Adresse bestätigen um Benachrichtigungen zu erhalten.',
|
||||
Your_certificate: 'Dein Zertifikat',
|
||||
Your_message: 'Deine Nachricht',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Dein Einladungs-Link wird nach {{usesLeft}} Benutzungen ablaufen.',
|
||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Dein Einladungs-Link wird am {{date}} oder nach {{usesLeft}} Benutzungen ablaufen.',
|
||||
Your_invite_link_will_expire_on__date__: 'Dein Einladungs-Link wird am {{date}} ablaufen.',
|
||||
|
@ -680,5 +680,29 @@ export default {
|
|||
No_threads: 'Es gibt keine Threads',
|
||||
No_threads_following: 'Du folgst keinen Threads',
|
||||
No_threads_unread: 'Es gibt keine ungelesenen Threads',
|
||||
Messagebox_Send_to_channel: 'an Kanal senden'
|
||||
Messagebox_Send_to_channel: 'an Kanal senden',
|
||||
Set_as_leader: 'Zum Diskussionsleiter ernennen',
|
||||
Set_as_moderator: 'Zum Moderator ernennen',
|
||||
Set_as_owner: 'Zum Besitzer machen',
|
||||
Remove_as_leader: 'Als Diskussionsleiter entfernen',
|
||||
Remove_as_moderator: 'Moderatorenrechte entfernen',
|
||||
Remove_as_owner: 'Als Eigentümer entfernen',
|
||||
Remove_from_room: 'Aus dem Raum entfernen',
|
||||
Ignore: 'Ignorieren',
|
||||
Unignore: 'Nicht mehr ignorieren',
|
||||
User_has_been_ignored: 'Benutzer wurde stumm geschaltet',
|
||||
User_has_been_unignored: 'Benutzer nicht mehr stumm geschaltet',
|
||||
User_has_been_removed_from_s: 'Benutzer wurde aus {{s}} entfernt',
|
||||
User__username__is_now_a_leader_of__room_name_: 'Benutzer {{username}} ist nun Diskussionsleiter von {{room_name}}',
|
||||
User__username__is_now_a_moderator_of__room_name_: 'Benutzer {{username}} ist nun Moderator von {{room_name}}',
|
||||
User__username__is_now_a_owner_of__room_name_: 'Benutzer {{username}} ist nun Eigentümer von {{room_name}}',
|
||||
User__username__removed_from__room_name__leaders: 'Benutzer {{username}} als Diskussionsleiter von {{room_name}} entfernt',
|
||||
User__username__removed_from__room_name__moderators: 'Benutzer {{username}} als Moderator von {{room_name}} entfernt',
|
||||
User__username__removed_from__room_name__owners: 'Benutzer {{username}} als Eigentümer von {{room_name}} entfernt',
|
||||
The_user_will_be_removed_from_s: 'Der Benutzer wird aus {{s}} entfernt',
|
||||
Yes_remove_user: 'Ja, Benutzer entfernen!',
|
||||
Direct_message: 'Direktnachricht',
|
||||
Message_Ignored: 'Nachricht ignoriert. Antippen um sie zu zeigen.',
|
||||
Enter_workspace_URL: 'Arbeitsbereich-URL',
|
||||
Workspace_URL_Example: 'z.B. https://rocketchat.deine-firma.de'
|
||||
};
|
||||
|
|
|
@ -223,7 +223,7 @@ export default {
|
|||
Enter_Your_Encryption_Password_desc1: 'This will allow you to access your encrypted private groups and direct messages.',
|
||||
Enter_Your_Encryption_Password_desc2: 'You need to enter the password to encode/decode messages every place you use the chat.',
|
||||
Encryption_error_title: 'Your encryption password seems wrong',
|
||||
Encryption_error_desc: 'Wasn\'t possible to decode your encryption key to be imported.',
|
||||
Encryption_error_desc: 'It wasn\'t possible to decode your encryption key to be imported.',
|
||||
Everyone_can_access_this_channel: 'Everyone can access this channel',
|
||||
Error_uploading: 'Error uploading',
|
||||
Expiration_Days: 'Expiration (Days)',
|
||||
|
@ -565,7 +565,6 @@ export default {
|
|||
Username: 'Username',
|
||||
Username_or_email: 'Username or email',
|
||||
Uses_server_configuration: 'Uses server configuration',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Usually, a discussion starts with a question, like "How do I upload a picture?"',
|
||||
Validating: 'Validating',
|
||||
Registration_Succeeded: 'Registration Succeeded!',
|
||||
Verify: 'Verify',
|
||||
|
@ -600,7 +599,6 @@ export default {
|
|||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
|
||||
You_need_to_verifiy_your_email_address_to_get_notications: 'You need to verify your email address to get notifications',
|
||||
Your_certificate: 'Your Certificate',
|
||||
Your_message: 'Your message',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Your invite link will expire after {{usesLeft}} uses.',
|
||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Your invite link will expire on {{date}} or after {{usesLeft}} uses.',
|
||||
Your_invite_link_will_expire_on__date__: 'Your invite link will expire on {{date}}.',
|
||||
|
@ -704,5 +702,7 @@ export default {
|
|||
The_user_will_be_removed_from_s: 'The user will be removed from {{s}}',
|
||||
Yes_remove_user: 'Yes, remove user!',
|
||||
Direct_message: 'Direct message',
|
||||
Message_Ignored: 'Message ignored. Tap to display it.'
|
||||
Message_Ignored: 'Message ignored. Tap to display it.',
|
||||
Enter_workspace_URL: 'Enter workspace URL',
|
||||
Workspace_URL_Example: 'Ex. your-company.rocket.chat'
|
||||
};
|
||||
|
|
|
@ -531,7 +531,6 @@ export default {
|
|||
Username: 'Nom d\'utilisateur',
|
||||
Username_or_email: 'Nom d\'utilisateur ou address e-mail',
|
||||
Uses_server_configuration: 'Utilise la configuration du serveur',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Habituellement, une discussion commence par une question, comme "Comment télécharger une image?"',
|
||||
Validating: 'Validation',
|
||||
Registration_Succeeded: 'Inscription réussie!',
|
||||
Verify: 'Vérifier',
|
||||
|
@ -565,7 +564,6 @@ export default {
|
|||
Logged_out_by_server: 'Vous avez été déconnecté par le serveur. Veuillez vous reconnecter.',
|
||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Vous devez accéder à au moins un serveur Rocket.Chat pour partager quelque chose.',
|
||||
Your_certificate: 'Votre Certificat',
|
||||
Your_message: 'Votre message',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Votre lien d\'invitation expirera après {{usesLeft}} utilisations.',
|
||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Votre lien d\'invitation expirera le {{date}} ou après {{usesLeft}} utilisations.',
|
||||
Your_invite_link_will_expire_on__date__: 'Votre lien d\'invitation expirera le {{date}}.',
|
||||
|
|
|
@ -197,6 +197,7 @@ export default {
|
|||
Do_you_have_an_account: 'Hai un account?',
|
||||
Do_you_have_a_certificate: 'Hai un certificato?',
|
||||
Do_you_really_want_to_key_this_room_question_mark: 'Sei sicuro di voler {{key}} questa stanza?',
|
||||
E2E_Encryption: 'Crittografia E2E',
|
||||
E2E_How_It_Works_info1: 'Ora puoi creare gruppi e messaggi privati crittografati. Puoi anche crittografare quelli già esistenti.',
|
||||
E2E_How_It_Works_info2: 'Questa è *crittografia end-to-end*, quindi la chiave per cifrare/decifrare i messaggi non verrà salvata sul server. Per questo motivo *devi salvare questa password in un luogo sicuro* dove poterla recuperare in seguito qualora necessario.',
|
||||
E2E_How_It_Works_info3: 'Procedendo verrà generata automaticamente una password E2E.',
|
||||
|
@ -280,6 +281,8 @@ export default {
|
|||
Invite_Link: 'Link di invito',
|
||||
Invite_users: 'Invita utenti',
|
||||
Join: 'Entra',
|
||||
Join_Code: 'Codice d\'accesso',
|
||||
Insert_Join_Code: 'Inserisci il codice d\'accesso',
|
||||
Join_our_open_workspace: 'Unisciti al nostro workspace',
|
||||
Join_your_workspace: 'Unisciti al tuo workspace',
|
||||
Just_invited_people_can_access_this_channel: 'Solo le persone invitate possono accedere a questo canale',
|
||||
|
@ -435,6 +438,7 @@ export default {
|
|||
Roles: 'Ruoli',
|
||||
Room_actions: 'Azioni stanza',
|
||||
Room_changed_announcement: 'Annuncio stanza cambiato in: {{announcement}} da {{userBy}}',
|
||||
Room_changed_avatar: 'Immagine stanza cambiata da {{userBy}}',
|
||||
Room_changed_description: 'Descrizione stanza cambiata in: {{description}} da {{userBy}}',
|
||||
Room_changed_privacy: 'Tipo stanza cambiato in: {{type}} da {{userBy}}',
|
||||
Room_changed_topic: 'Argomento stanza cambiato in: {{topic}} da {{userBy}}',
|
||||
|
@ -461,6 +465,7 @@ export default {
|
|||
Search_global_users: 'Cerca utenti globali',
|
||||
Search_global_users_description: 'Se attivi questa opzione, puoi cercare qualsiasi utente da altre aziende o server.',
|
||||
Seconds: '{{second}} secondi',
|
||||
Security_and_privacy: 'Sicurezza e privacy',
|
||||
Select_Avatar: 'Seleziona avatar',
|
||||
Select_Server: 'Seleziona server',
|
||||
Select_Users: 'Seleziona utenti',
|
||||
|
@ -533,7 +538,7 @@ export default {
|
|||
unarchive: 'rimuovi dall\'archivio',
|
||||
UNARCHIVE: 'RIMUOVI DALL\'ARCHIVIO',
|
||||
Unblock_user: 'Sblocca utente',
|
||||
Unfavorite: 'Rimuovi dai preferiti',
|
||||
Unfavorite: 'Rimuovi preferito',
|
||||
Unfollowed_thread: 'Non segui più il thread',
|
||||
Unmute: 'Attiva notifiche',
|
||||
unmuted: 'notifiche attivate',
|
||||
|
@ -560,7 +565,6 @@ export default {
|
|||
Username: 'Username',
|
||||
Username_or_email: 'Username o email',
|
||||
Uses_server_configuration: 'Usa la configurazione del server',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Una discussione inizia solitamente con una domanda, ad esempio: "Come posso caricare una foto?"',
|
||||
Validating: 'Validazione',
|
||||
Registration_Succeeded: 'Registrazione completata!',
|
||||
Verify: 'Verifica',
|
||||
|
@ -595,7 +599,6 @@ export default {
|
|||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Devi accedere ad almeno un server Rocket.Chat prima di condividere qualcosa.',
|
||||
You_need_to_verifiy_your_email_address_to_get_notications: 'Devi verificare il tuo indirizzo e-mail per ricevere le notifiche',
|
||||
Your_certificate: 'Il tuo certificato',
|
||||
Your_message: 'Il tuo messaggio',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Il tuo link di invito scadrà dopo {{usesLeft}} utilizzi.',
|
||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Il tuo link di invito scadrà il {{date}} oppure dopo {{usesLeft}} utilizzi.',
|
||||
Your_invite_link_will_expire_on__date__: 'Il tuo link di invito scadrà il {{date}}.',
|
||||
|
@ -657,5 +660,49 @@ export default {
|
|||
You_will_be_logged_out_from_other_locations: 'Verrai disconnesso dalle altre postazioni.',
|
||||
Logged_out_of_other_clients_successfully: 'Disconnesso dalle altre postazioni con successo',
|
||||
Logout_failed: 'Disconnessione fallita!',
|
||||
Log_analytics_events: 'Invia statistiche anonime'
|
||||
Log_analytics_events: 'Invia statistiche anonime',
|
||||
E2E_encryption_change_password_title: 'Cambia la password di cifratura',
|
||||
E2E_encryption_change_password_description: 'Ora puoi creare gruppi e messaggi privati crittografati. Potrai, inoltre, crittografare i tuoi gruppi e messaggi privati esistenti. \nQuesta è crittografia end-to-end, la chiave per codificare/decodificare i tuoi messaggi non verrà salvata sul server. Per questo motivo devi salvarla in un luogo sicuro. Ti verrà richiesto di inserirla sugli altri dispositivi sui quali vuoi usare la crittografia e2e.',
|
||||
E2E_encryption_change_password_error: 'Si è verificato un errore durante il cambio della password della chiave E2E!',
|
||||
E2E_encryption_change_password_success: 'La password della chiave E2E è stata cambiata con successo!',
|
||||
E2E_encryption_change_password_message: 'Assicurati di salvarla in un posto sicuro.',
|
||||
E2E_encryption_change_password_confirmation: 'Si, cambiala',
|
||||
E2E_encryption_reset_title: 'Ripristina la Chiave E2E',
|
||||
E2E_encryption_reset_description: 'Questa opzione rimuoverà la tua chiave E2E corrente e sarai disconnesso. \nAl prossimo login, Rocket.Chat genererà una nuova chiave e ripristinerà l\'accesso a tutte le stanze crittografate con uno o più membri online. \nA causa della natura della crittografia E2E, Rocket.Chat non sarà in grado di ripristinare l\'accesso alle stanze senza membri online.',
|
||||
E2E_encryption_reset_button: 'Ripristina',
|
||||
E2E_encryption_reset_error: 'Si è verificato un errore durante il ripristino della chiave E2E!',
|
||||
E2E_encryption_reset_message: 'Stai per essere disconnesso.',
|
||||
E2E_encryption_reset_confirmation: 'Si, resettala',
|
||||
Following: 'Seguiti',
|
||||
Threads_displaying_all: 'Visualizza Tutti',
|
||||
Threads_displaying_following: 'Visualizza Seguiti',
|
||||
Threads_displaying_unread: 'Visualizza Non letti',
|
||||
No_threads: 'Non ci sono thread',
|
||||
No_threads_following: 'Non stai seguendo alcun thread',
|
||||
No_threads_unread: 'Non ci sono thread non letti',
|
||||
Messagebox_Send_to_channel: 'Invia sul canale',
|
||||
Set_as_leader: 'Rendi leader',
|
||||
Set_as_moderator: 'Rendi moderatore',
|
||||
Set_as_owner: 'Rendi proprietario',
|
||||
Remove_as_leader: 'Rimuovi come leader',
|
||||
Remove_as_moderator: 'Rimuovi come moderatore',
|
||||
Remove_as_owner: 'Rimuovi come proprietario',
|
||||
Remove_from_room: 'Rimuovi dalla stanza',
|
||||
Ignore: 'Ignora',
|
||||
Unignore: 'Non ignorare',
|
||||
User_has_been_ignored: 'Utente ignorato',
|
||||
User_has_been_unignored: 'Utente non ignorato',
|
||||
User_has_been_removed_from_s: 'L\'utente è stato rimosso da {{s}}',
|
||||
User__username__is_now_a_leader_of__room_name_: 'L\'utente {{username}} è ora un leader di {{room_name}}',
|
||||
User__username__is_now_a_moderator_of__room_name_: 'L\'utente {{username}} è ora un moderatore di {{room_name}}',
|
||||
User__username__is_now_a_owner_of__room_name_: 'L\'utente {{username}} è ora un proprietario di {{room_name}}',
|
||||
User__username__removed_from__room_name__leaders: 'L\'utente {{username}} non è più un leader di {{room_name}}',
|
||||
User__username__removed_from__room_name__moderators: 'L\'utente {{username}} non è più un moderatore di {{room_name}}',
|
||||
User__username__removed_from__room_name__owners: 'L\'utente {{username}} non è più un proprietario di {{room_name}}',
|
||||
The_user_will_be_removed_from_s: 'L\'utente sarà rimosso da {{s}}',
|
||||
Yes_remove_user: 'Si, rimuovi utente!',
|
||||
Direct_message: 'Messaggio diretto',
|
||||
Message_Ignored: 'Messaggio ignorato. Tocca per visualizzarlo.',
|
||||
Enter_workspace_URL: 'Inserisci la url del workspace',
|
||||
Workspace_URL_Example: 'Es. tua-azienda.rocket.chat'
|
||||
};
|
||||
|
|
|
@ -522,7 +522,6 @@ export default {
|
|||
Username: 'Usuário',
|
||||
Username_or_email: 'Usuário ou email',
|
||||
Uses_server_configuration: 'Usar configuração do servidor',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Normalmente, uma discussão começa com uma pergunta como: Como faço para enviar uma foto?',
|
||||
Verify: 'Verificar',
|
||||
Verify_email_title: 'Registrado com sucesso!',
|
||||
Verify_email_desc: 'Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.',
|
||||
|
@ -541,7 +540,6 @@ export default {
|
|||
You_are_in_preview_mode: 'Está é uma prévia do canal',
|
||||
You_are_offline: 'Você está offline',
|
||||
You_can_search_using_RegExp_eg: 'Você pode usar expressões regulares, por exemplo `/^text$/i`',
|
||||
Your_message: 'Sua mensagem',
|
||||
You_need_to_verifiy_your_email_address_to_get_notications: 'Você precisa confirmar seu endereço de e-mail para obter notificações',
|
||||
You_colon: 'Você: ',
|
||||
you_were_mentioned: 'você foi mencionado',
|
||||
|
@ -651,5 +649,7 @@ export default {
|
|||
The_user_will_be_removed_from_s: 'O usuário será removido de {{s}}',
|
||||
Yes_remove_user: 'Sim, remover usuário!',
|
||||
Direct_message: 'Mensagem direta',
|
||||
Message_Ignored: 'Mensagem ignorada. Toque para mostrar.'
|
||||
Message_Ignored: 'Mensagem ignorada. Toque para mostrar.',
|
||||
Enter_workspace_URL: 'Digite a URL da sua workspace',
|
||||
Workspace_URL_Example: 'Ex. sua-empresa.rocket.chat'
|
||||
};
|
||||
|
|
|
@ -563,7 +563,6 @@ export default {
|
|||
Username: 'Имя пользователя',
|
||||
Username_or_email: 'Имя пользователя или email',
|
||||
Uses_server_configuration: 'Используется конфигурация сервера',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Обычно Обсуждение начинается с какого-либо вопроса, например, "Как мне загрузить эту картинку?"',
|
||||
Validating: 'Проверка',
|
||||
Registration_Succeeded: 'Регистрация Успешна!',
|
||||
Verify: 'Проверить',
|
||||
|
@ -598,7 +597,6 @@ export default {
|
|||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Вам нужно получить доступ как минимум к одному серверу Rocket.Chat, чтобы поделиться чем-то.',
|
||||
You_need_to_verifiy_your_email_address_to_get_notications: 'Вам необходимо проверить ваш email адрес, чтобы получать уведомления',
|
||||
Your_certificate: 'Ваш сертификат',
|
||||
Your_message: 'Ваше сообщение',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Ваша ссылка-приглашение станет не действительной после {{usesLeft}} ее использований.',
|
||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Ваша ссылка-приглашение станет не действительной {{date}} или после {{usesLeft}} ее использований.',
|
||||
Your_invite_link_will_expire_on__date__: 'Срок действия вашей ссылки-приглашения будет окончен {{date}}.',
|
||||
|
|
|
@ -0,0 +1,710 @@
|
|||
export default {
|
||||
'1_person_reacted': '1 kişi tepki verdi.',
|
||||
'1_user': '1 kullanıcı',
|
||||
'error-action-not-allowed': '{{action}}\'a izin verilmiyor!',
|
||||
'error-application-not-found': 'Uygulama bulunamadı!',
|
||||
'error-archived-duplicate-name': '{{room_name}} adında arşivlenmiş bir kanal var!',
|
||||
'error-avatar-invalid-url': 'Geçersiz avatar URL\'si: {{url}}',
|
||||
'error-avatar-url-handling': '{{username}} için bir URL\'den ({{url}}) avatar ayarı işlenirken hata oluştu!',
|
||||
'error-cant-invite-for-direct-room': 'Kullanıcı özel odalara davet edilemedi!',
|
||||
'error-could-not-change-email': 'E-posta değiştirilemedi!',
|
||||
'error-could-not-change-name': 'İsim değiştirilemedi!',
|
||||
'error-could-not-change-username': 'Kullanıcı adı değiştirilemedi!',
|
||||
'error-could-not-change-status': 'Durum değiştirilemedi!',
|
||||
'error-delete-protected-role': 'Korunan bir rol silinemez!',
|
||||
'error-department-not-found': 'Bölüm bulunamadı!',
|
||||
'error-direct-message-file-upload-not-allowed': 'Özel iletilerde dosya paylaşımına izin verilmiyor!',
|
||||
'error-duplicate-channel-name': '{{channel_name}} adında bir kanal var!',
|
||||
'error-email-domain-blacklisted': 'E-posta alan adı kara listeye alındı!',
|
||||
'error-email-send-failed': 'E-posta göndermeye çalışırken hata oluştu: {{message}}',
|
||||
'error-save-image': 'Görüntüyü kaydederken hata oluştu!',
|
||||
'error-save-video': 'Videoyu kaydederken hata oluştu!',
|
||||
'error-field-unavailable': '{{field}} zaten kullanılıyor! :(',
|
||||
'error-file-too-large': 'Dosya çok büyük!',
|
||||
'error-importer-not-defined': 'İçe aktarıcı doğru tanımlanmadı, "Import" sınıfı eksik!',
|
||||
'error-input-is-not-a-valid-field': '{{input}} geçerli bir {{field}} değil!',
|
||||
'error-invalid-actionlink': 'Geçersiz işlem bağlantısı!',
|
||||
'error-invalid-arguments': 'Geçersiz parametreler!',
|
||||
'error-invalid-asset': 'Geçersiz veri!',
|
||||
'error-invalid-channel': 'Geçersiz kanal.',
|
||||
'error-invalid-channel-start-with-chars': 'Geçersiz kanal! @ veya # ile başlayın.',
|
||||
'error-invalid-custom-field': 'Geçersiz özelleştirilmiş alan',
|
||||
'error-invalid-custom-field-name': 'Geçersiz özelleştirilmiş alan adı! Yalnızca harf, rakam, kısa çizgi ve alt çizgi kullanın.',
|
||||
'error-invalid-date': 'Geçersiz tarih!',
|
||||
'error-invalid-description': 'Geçersiz açıklama!',
|
||||
'error-invalid-domain': 'Geçersiz alan adı!',
|
||||
'error-invalid-email': 'Geçersiz e-posta {{email}}!',
|
||||
'error-invalid-email-address': 'Geçersiz e-posta adresi!',
|
||||
'error-invalid-file-height': 'Geçersiz fotoğraf yüksekliği!',
|
||||
'error-invalid-file-type': 'Geçersiz dosya türü!',
|
||||
'error-invalid-file-width': 'Geçersiz fotoğraf genişliği!',
|
||||
'error-invalid-from-address': 'Geçersiz bir KİMDEN adresi bildirdiniz!',
|
||||
'error-invalid-integration': 'Geçersiz entegrasyon',
|
||||
'error-invalid-message': 'Geçersiz ileti!',
|
||||
'error-invalid-method': 'Geçersiz metot!',
|
||||
'error-invalid-name': 'Geçersiz isim!',
|
||||
'error-invalid-password': 'Geçersiz şifre!',
|
||||
'error-invalid-redirectUri': 'Geçersiz yönlendirme bağlantısı!',
|
||||
'error-invalid-role': 'Geçersiz rol!',
|
||||
'error-invalid-room': 'Geçersiz oda!',
|
||||
'error-invalid-room-name': '{{room_name}}, geçerli bir oda adı değil!',
|
||||
'error-invalid-room-type': '{{type}}, geçerli bir oda türü değil!',
|
||||
'error-invalid-settings': 'Geçersiz ayar!',
|
||||
'error-invalid-subscription': 'Geçersiz başvuru!',
|
||||
'error-invalid-token': 'Geçersiz belirteç!',
|
||||
'error-invalid-triggerWords': 'Geçersiz tetikleyici parametreleri!',
|
||||
'error-invalid-urls': 'Geçersiz URL\'ler!',
|
||||
'error-invalid-user': 'Geçersiz kullanıcı!',
|
||||
'error-invalid-username': 'Geçersiz kullanıcı adı!',
|
||||
'error-invalid-webhook-response': 'İstek URL\'si 200\'den farklı bir durumla yanıt verdi!',
|
||||
'error-message-deleting-blocked': 'İleti silme engellendi!',
|
||||
'error-message-editing-blocked': 'İleti düzenleme engellendi',
|
||||
'error-message-size-exceeded': 'İleti boyutu, Message_MaxAllowedSize değerini aşıyor',
|
||||
'error-missing-unsubscribe-link': '[unsubscribe] bağlantısını belirtmelisiniz.',
|
||||
'error-no-tokens-for-this-user': 'Bu kullanıcı için belirteç (token) yok',
|
||||
'error-not-allowed': 'İzin verilmedi',
|
||||
'error-not-authorized': 'Yetkili değil',
|
||||
'error-push-disabled': 'Push devre dışı',
|
||||
'error-remove-last-owner': 'Lütfen bunu kaldırmadan önce yeni bir sahip belirleyin.',
|
||||
'error-role-in-use': 'Rol kullanımda olduğu için silinemiyor',
|
||||
'error-role-name-required': 'Rol adı gerekli',
|
||||
'error-the-field-is-required': '{{field}} alanı gereklidir.',
|
||||
'error-too-many-requests': 'Hata, çok fazla istek. Lütfen yavaşla. Tekrar denemeden önce {{seconds}} saniye beklemelisiniz.',
|
||||
'error-user-is-not-activated': 'Kullanıcı etkinleştirilmedi!',
|
||||
'error-user-has-no-roles': 'Kullanıcıya tanımlı rol yok!',
|
||||
'error-user-limit-exceeded': '#channel_name kanalına davet etmeye çalıştığınız kullanıcıların sayısı, yönetici tarafından belirlenen sınırı aşıyor!',
|
||||
'error-user-not-in-room': 'Kullanıcı bu odada değil!',
|
||||
'error-user-registration-custom-field': 'error-user-registration-custom-field',
|
||||
'error-user-registration-disabled': 'Kullanıcı kaydı devre dışı bırakıldı!',
|
||||
'error-user-registration-secret': 'Kullanıcı kaydına yalnızca Gizli URL aracılığıyla izin verilir!',
|
||||
'error-you-are-last-owner': 'Son sahibi sizsiniz. Lütfen odadan ayrılmadan önce yeni bir sahip belirleyin.',
|
||||
Actions: 'İşlemler',
|
||||
activity: 'etkinlik',
|
||||
Activity: 'Etkinlik',
|
||||
Add_Reaction: 'Tepki ekle',
|
||||
Add_Server: 'Sunucu ekle',
|
||||
Add_users: 'Kullanıcı ekle',
|
||||
Admin_Panel: 'Yönetim Paneli',
|
||||
Agent: 'Temsilci',
|
||||
Alert: 'Uyarı',
|
||||
alert: 'uyarı',
|
||||
alerts: 'uyarılar',
|
||||
All_users_in_the_channel_can_write_new_messages: 'Kanaldaki tüm kullanıcılar yeni ileti yazabilir!',
|
||||
A_meaningful_name_for_the_discussion_room: 'Tartışma odası için anlamlı bir isim',
|
||||
All: 'Tümü',
|
||||
All_Messages: 'Tüm İletiler',
|
||||
Allow_Reactions: 'Tüm Tepkiler',
|
||||
Alphabetical: 'Alfabetik',
|
||||
and_more: 've daha',
|
||||
and: 've',
|
||||
announcement: 'duyuru',
|
||||
Announcement: 'Duyuru',
|
||||
Apply_Your_Certificate: 'Sertifikanızı Onaylayın',
|
||||
ARCHIVE: 'ARŞİVLE',
|
||||
archive: 'arşivle',
|
||||
are_typing: 'yazıyor',
|
||||
Are_you_sure_question_mark: 'Emin misiniz?',
|
||||
Are_you_sure_you_want_to_leave_the_room: '{{room}} odasından ayrılmak istediğinizden emin misiniz?',
|
||||
Audio: 'Ses',
|
||||
Authenticating: 'Doğrulanıyor',
|
||||
Automatic: 'Otomatik',
|
||||
Auto_Translate: 'Otomatik Çevir',
|
||||
Avatar_changed_successfully: 'Profil fotoğrafı başarıyla değiştirildi!',
|
||||
Avatar_Url: 'Profil fotoğrafı URL\'si',
|
||||
Away: 'Uzakta',
|
||||
Back: 'Geri',
|
||||
Black: 'Koyu',
|
||||
Block_user: 'Kullanıcıyı engelle',
|
||||
Browser: 'Tarayıcı',
|
||||
Broadcast_channel_Description: 'Yalnızca yetkili kullanıcılar yeni ileti yazabilir, ancak diğer kullanıcılar yanıt verebilir',
|
||||
Broadcast_Channel: 'Kanala Yayınla',
|
||||
Busy: 'Meşgul',
|
||||
By_proceeding_you_are_agreeing: 'Devam ederek kabul ediyorsunuz: ',
|
||||
Cancel_editing: 'Düzenlemeyi iptal et',
|
||||
Cancel_recording: 'Kaydı iptal et',
|
||||
Cancel: 'İptal et',
|
||||
changing_avatar: 'profil fotoğrafı değiştirme',
|
||||
creating_channel: 'kanal açılıyor',
|
||||
creating_invite: 'davet üretiliyor',
|
||||
Channel_Name: 'Kanal Adı',
|
||||
Channels: 'Kanallar',
|
||||
Chats: 'Sohbetler',
|
||||
Call_already_ended: 'Çağrı sona erdi!',
|
||||
Clear_cookies_alert: 'Tüm çerezleri temizlemek istiyor musunuz?',
|
||||
Clear_cookies_desc: 'Bu işlem, tüm oturum açma çerezlerini temizleyerek diğer hesaplara giriş yapmanıza olanak tanır.',
|
||||
Clear_cookies_yes: 'Evet, çerezleri temizle',
|
||||
Clear_cookies_no: 'Hayır, çerezleri koru',
|
||||
Click_to_join: 'Katılmak için tıklayın!',
|
||||
Close: 'Kapat',
|
||||
Close_emoji_selector: 'Emoji seçiciyi kapat',
|
||||
Closing_chat: 'Sohbet kapatılıyor',
|
||||
Change_language_loading: 'Dil değiştiriliyor',
|
||||
Chat_closed_by_agent: 'Sohbet temsilci tarafından kapatıldı',
|
||||
Choose: 'Seç',
|
||||
Choose_from_library: 'Kütüphaneden seç',
|
||||
Choose_file: 'Dosya seç',
|
||||
Choose_where_you_want_links_be_opened: 'Bağlantıların açılmasını istediğiniz yeri seçin',
|
||||
Code: 'Kod',
|
||||
Code_or_password_invalid: 'Kod veya şifre geçersiz',
|
||||
Collaborative: 'İşbirlikçi',
|
||||
Confirm: 'Onayla',
|
||||
Connect: 'Bağlan',
|
||||
Connected: 'Bağlandı',
|
||||
connecting_server: 'sunucuya bağlanıyor',
|
||||
Connecting: 'Bağlanıyor...',
|
||||
Contact_us: 'Bize ulaşın',
|
||||
Contact_your_server_admin: 'Sunucu yöneticisiyle iletişime geçin.',
|
||||
Continue_with: 'Devam et:',
|
||||
Copied_to_clipboard: 'Panoya kopyalandı!',
|
||||
Copy: 'Kopyala',
|
||||
Conversation: 'Sohbet',
|
||||
Permalink: 'Kalıcı bağlantı',
|
||||
Certificate_password: 'Sertifika Şifresi',
|
||||
Clear_cache: 'Yerel sunucu önbelleğini temizleyin',
|
||||
Clear_cache_loading: 'Önbellek temizleniyor.',
|
||||
Whats_the_password_for_your_certificate: 'Sertifikanızın şifresi nedir?',
|
||||
Create_account: 'Hesap oluştur',
|
||||
Create_Channel: 'Kanal Oluştur',
|
||||
Create_Direct_Messages: 'Özel İleti Oluştur',
|
||||
Create_Discussion: 'Tartışma Oluştur',
|
||||
Created_snippet: 'kalıp oluşturdu',
|
||||
Create_a_new_workspace: 'Çalışma alanı oluştur',
|
||||
Create: 'Oluştur',
|
||||
Custom_Status: 'Özelleştirilmiş durum',
|
||||
Dark: 'Karanlık',
|
||||
Dark_level: 'Karanlık Seviyesi',
|
||||
Default: 'Varsayılan',
|
||||
Default_browser: 'Varsayılan tarayıcı',
|
||||
Delete_Room_Warning: 'Bir odanın silinmesi, oda içinde yayınlanan tüm iletileri silecektir. Bu geri alınamaz.',
|
||||
Department: 'Bölüm',
|
||||
delete: 'sil',
|
||||
Delete: 'Sil',
|
||||
DELETE: 'SİL',
|
||||
deleting_room: 'oda siliniyor',
|
||||
description: 'açıklama',
|
||||
Description: 'Açıklama',
|
||||
Desktop_Options: 'Masaüstü Seçenekleri',
|
||||
Desktop_Notifications: 'Masaüstü Bildirimleri',
|
||||
Desktop_Alert_info: 'Bu bildirimler masaüstünde teslim edilir',
|
||||
Directory: 'Dizin',
|
||||
Direct_Messages: 'Özel İletiler',
|
||||
Disable_notifications: 'Bildirimleri devre dışı bırak',
|
||||
Discussions: 'Tartışmalar',
|
||||
Discussion_Desc: 'Neler olup bittiğini gözden geçirmeye yardımcı olun! Bir tartışma oluşturarak, seçtiğinizin bir alt kanalı oluşturulur ve her ikisi de bağlanır.',
|
||||
Discussion_name: 'Tartışma adı',
|
||||
Done: 'Tamamlandı',
|
||||
Dont_Have_An_Account: 'Hesabın yok mu?',
|
||||
Do_you_have_an_account: 'Hesabın var mı?',
|
||||
Do_you_have_a_certificate: 'Sertifikanız var mı?',
|
||||
Do_you_really_want_to_key_this_room_question_mark: 'Bu odayı gerçekten {{key}} istiyor musun?',
|
||||
E2E_Encryption: 'Uçtan Uca Şifreleme (E2E)',
|
||||
E2E_How_It_Works_info1: 'Artık şifrelenmiş özel gruplar ve doğrudan iletiler oluşturabilirsiniz. Mevcut özel grupları veya özel iletileri şifreli olarak da değiştirebilirsiniz.',
|
||||
E2E_How_It_Works_info2: 'Bu, *Uçtan Uca Şifreleme\'dir (E2E)*, böylece iletilerinizi şifrelemek / çözmek için anahtar ve bunlar sunucuya kaydedilmez. Bu nedenle *bu şifreyi daha sonra ihtiyaç duyduğunuzda erişebileceğiniz güvenli bir yerde* saklamanız gerekir.',
|
||||
E2E_How_It_Works_info3: 'Devam ederseniz, otomatik olarak bir Uçtan Uca Şifreleme (E2E) şifresi oluşturulacaktır.',
|
||||
E2E_How_It_Works_info4: 'Mevcut Uçtan Uca Şifreleme (E2E) şifresini, herhangi bir tarayıcıdan istediğiniz zaman girdiğiniz şifreleme anahtarınız için yeni bir şifre de ayarlayabilirsiniz.',
|
||||
edit: 'düzenle',
|
||||
edited: 'düzenlendi',
|
||||
Edit: 'Düzenle',
|
||||
Edit_Status: 'Durumu Düzenle',
|
||||
Edit_Invite: 'Daveti Düzenle',
|
||||
End_to_end_encrypted_room: 'Uçtan uca şifreli oda',
|
||||
end_to_end_encryption: 'uçtan uca şifreleme',
|
||||
Email_Notification_Mode_All: 'Tüm Bahsetmeler/Özel İletiler',
|
||||
Email_Notification_Mode_Disabled: 'Devre Dışı',
|
||||
Email_or_password_field_is_empty: 'E-posta veya şifre alanı boş',
|
||||
Email: 'E-posta',
|
||||
email: 'e-posta',
|
||||
Empty_title: 'Boş başlık',
|
||||
Enable_Auto_Translate: 'Otomatik Çeviriyi Etkinleştir',
|
||||
Enable_notifications: 'Bildirimleri etkinleştir',
|
||||
Encrypted: 'Şifreli',
|
||||
Encrypted_message: 'Şifreli ileti',
|
||||
Enter_Your_E2E_Password: 'Uçtan Uca Şifreleme (E2E) Şifrenizi Girin',
|
||||
Enter_Your_Encryption_Password_desc1: 'Bu, şifrelenmiş özel gruplarınıza ve doğrudan iletilerinize erişmenize izin verecektir.',
|
||||
Enter_Your_Encryption_Password_desc2: 'Sohbeti kullandığınız her yerde iletileri şifrelemek / çözmek için şifre girmeniz gerekir.',
|
||||
Encryption_error_title: 'Şifreleme şifreniz yanlış görünüyor',
|
||||
Encryption_error_desc: 'İçe aktarılacak şifreleme anahtarınızın kodu çözülemedi.',
|
||||
Everyone_can_access_this_channel: 'Bu kanala herkes erişebilir',
|
||||
Error_uploading: 'Yükleme hatası',
|
||||
Expiration_Days: 'Geçerlilik Süresi (Gün)',
|
||||
Favorite: 'Favori',
|
||||
Favorites: 'Favoriler',
|
||||
Files: 'Dosyalar',
|
||||
File_description: 'Dosya açıklaması',
|
||||
File_name: 'Dosya adı',
|
||||
Finish_recording: 'Kaydı bitir',
|
||||
Following_thread: 'Konu takip ediliyor',
|
||||
For_your_security_you_must_enter_your_current_password_to_continue: 'Güvenliğiniz için, devam etmek için mevcut şifrenizi girmelisiniz',
|
||||
Forgot_password_If_this_email_is_registered: 'Bu e-posta kayıtlıysa, şifrenizi nasıl sıfırlayacağınıza dair talimatlar göndereceğiz. Kısa süre içinde bir e-posta almazsanız, lütfen geri gelin ve tekrar deneyin.',
|
||||
Forgot_password: 'Parolanızı mı unuttunuz?',
|
||||
Forgot_Password: 'Parolamı Unuttum',
|
||||
Forward: 'İlet',
|
||||
Forward_Chat: 'Sohbete İlet',
|
||||
Forward_to_department: 'Bölüme İlet',
|
||||
Forward_to_user: 'Kullanıcıya İlet',
|
||||
Full_table: 'Tam tabloyu görmek için tıklayın',
|
||||
Generate_New_Link: 'Yeni Bağlantı Oluştur',
|
||||
Group_by_favorites: 'Favorilere göre grupla',
|
||||
Group_by_type: 'Türe göre grupla',
|
||||
Hide: 'Gizle',
|
||||
Has_joined_the_channel: 'kanala katıldı',
|
||||
Has_joined_the_conversation: 'sohbete katıldı',
|
||||
Has_left_the_channel: 'kanaldan ayrıldı',
|
||||
Hide_System_Messages: 'Sistem İletilerını Gizle',
|
||||
Hide_type_messages: '"{{type}}" iletilerini gizle',
|
||||
How_It_Works: 'Nasıl Çalışır',
|
||||
Message_HideType_uj: 'Kullanıcı Katıldı',
|
||||
Message_HideType_ul: 'Kullanıcı Ayrıldı',
|
||||
Message_HideType_ru: 'Kullanıcı Kaldırıldı',
|
||||
Message_HideType_au: 'Kullanıcı Eklendi',
|
||||
Message_HideType_mute_unmute: 'Kullanıcı Sesi Kapatıldı / Sesi Açıldı',
|
||||
Message_HideType_r: 'Oda Adı Değiştirildi',
|
||||
Message_HideType_ut: 'Kullanıcı Sohbete Katıldı',
|
||||
Message_HideType_wm: 'Hoşgeldiniz',
|
||||
Message_HideType_rm: 'İleti Kaldırıldı',
|
||||
Message_HideType_subscription_role_added: 'Rol Belirlendi',
|
||||
Message_HideType_subscription_role_removed: 'Artık Kullanılmayan Rol',
|
||||
Message_HideType_room_archived: 'Oda Arşivlendi',
|
||||
Message_HideType_room_unarchived: 'Oda Arşivden Çıkarıldı',
|
||||
I_Saved_My_E2E_Password: 'Uçtan Uca Şifreleme (E2E) Şifremi Kaydettim',
|
||||
IP: 'IP',
|
||||
In_app: 'Uygulama İçi',
|
||||
In_App_And_Desktop: 'Uygulama İçi ve Masaüstü',
|
||||
In_App_and_Desktop_Alert_info: 'Uygulama açıkken ekranın üst kısmında bir başlık görüntüler ve masaüstünde bir bildirim görüntüler',
|
||||
Invisible: 'Görünmez',
|
||||
Invite: 'Davet Et',
|
||||
is_a_valid_RocketChat_instance: 'geçerli bir Rocket.Chat örneği',
|
||||
is_not_a_valid_RocketChat_instance: 'geçerli bir Rocket.Chat örneği değil',
|
||||
is_typing: 'yazıyor',
|
||||
Invalid_or_expired_invite_token: 'Geçersiz veya süresi dolmuş davet belirteci (token)',
|
||||
Invalid_server_version: 'Bağlanmaya çalıştığınız sunucu artık uygulama tarafından desteklenmeyen bir sürüm kullanıyor: {{currentVersion}}.\n\n{{minVersion}} sürümüne ihtiyacımız var',
|
||||
Invite_Link: 'Davet Bağlantısı',
|
||||
Invite_users: 'Kullanıcıları davet et',
|
||||
Join: 'Katıl',
|
||||
Join_Code: 'Katılım Kodu',
|
||||
Insert_Join_Code: 'Katılım Kodu Girin',
|
||||
Join_our_open_workspace: 'Açık çalışma alanımıza katılın',
|
||||
Join_your_workspace: 'Çalışma alanınıza katılın',
|
||||
Just_invited_people_can_access_this_channel: 'Yalnızca davet edilen kişiler bu kanala erişebilir',
|
||||
Language: 'Dil',
|
||||
last_message: 'son ileti',
|
||||
Leave_channel: 'Kanaldan ayrıl',
|
||||
leaving_room: 'odadan ayrılıyor',
|
||||
leave: 'ayrıl',
|
||||
Legal: 'Yasal',
|
||||
Light: 'Açık',
|
||||
License: 'Lisans',
|
||||
Livechat: 'Canlı Sohbet',
|
||||
Livechat_edit: 'Canlı sohbeti düzenle',
|
||||
Login: 'Oturum aç',
|
||||
Login_error: 'Kimlik bilgileriniz reddedildi! Lütfen tekrar deneyin.',
|
||||
Login_with: 'ile giriş',
|
||||
Logging_out: 'Çıkış Yapılıyor',
|
||||
Logout: 'Çıkış Yap',
|
||||
Max_number_of_uses: 'Maksimum kullanım sayısı',
|
||||
Max_number_of_users_allowed_is_number: 'İzin verilen maksimum kullanıcı sayısı {{maxUsers}}',
|
||||
members: 'üyeler',
|
||||
Members: 'Üyeler',
|
||||
Mentioned_Messages: 'Bahsedilen İletiler',
|
||||
mentioned: 'bahsedildi',
|
||||
Mentions: 'Bahsetmeler',
|
||||
Message_accessibility: '{{user}} tarafından {{time}} itibarıyla ileti: {{message}}',
|
||||
Message_actions: 'İleti işlemleri',
|
||||
Message_pinned: 'İleti sabitlendi',
|
||||
Message_removed: 'İleti kaldırıldı',
|
||||
Message_starred: 'İletia yıldız eklendi',
|
||||
Message_unstarred: 'İletiın yıldızı kaldırıldı',
|
||||
message: 'ileti',
|
||||
messages: 'iletiler',
|
||||
Message: 'İleti',
|
||||
Messages: 'İletiler',
|
||||
Message_Reported: 'İleti bildirildi',
|
||||
Microphone_Permission_Message: 'Rocket.Chat\'in mikrofonunuza erişmesi gerekiyor, böylece sesli ileti gönderebilirsiniz.',
|
||||
Microphone_Permission: 'Mikrofon İzni',
|
||||
Mute: 'Sessize Al',
|
||||
muted: 'sessize alındı',
|
||||
My_servers: 'Sunucularım',
|
||||
N_people_reacted: '{{n}} kişi tepki verdi',
|
||||
N_users: '{{n}} kullanıcı',
|
||||
name: 'isim',
|
||||
Name: 'İsim',
|
||||
Navigation_history: 'Gezinti geçmişi',
|
||||
Never: 'Asla',
|
||||
New_Message: 'Yeni İleti',
|
||||
New_Password: 'Yeni Şifre',
|
||||
New_Server: 'Yeni Sunucu',
|
||||
Next: 'Sonraki',
|
||||
No_files: 'Dosya yok',
|
||||
No_limit: 'Limit yok',
|
||||
No_mentioned_messages: 'Belirtilen ileti yok',
|
||||
No_pinned_messages: 'Sabitlenmiş ileti yok',
|
||||
No_results_found: 'Sonuç bulunamadı',
|
||||
No_starred_messages: 'Yıldızlı ileti yok',
|
||||
No_thread_messages: 'Konu iletisi yok',
|
||||
No_label_provided: 'Hiç {{label}} sağlanmadı.',
|
||||
No_Message: 'İleti Yok',
|
||||
No_messages_yet: 'Şu ana kadar ileti yok',
|
||||
No_Reactions: 'Tepki Yok',
|
||||
No_Read_Receipts: 'Okundu Bilgisi Yok',
|
||||
Not_logged: 'Kayıtlı değil',
|
||||
Not_RC_Server: 'Bu bir Rocket.Chat sunucusu değil.\n{{contact}}',
|
||||
Nothing: 'Hiçbir Şey',
|
||||
Nothing_to_save: 'Kaydedilecek bir şey yok!',
|
||||
Notify_active_in_this_room: 'Bu odadaki çevrimiçi kullanıcıları bilgilendir',
|
||||
Notify_all_in_this_room: 'Bu odadaki herkesi bilgilendir',
|
||||
Notifications: 'Bildirimler',
|
||||
Notification_Duration: 'Bildirim Süresi',
|
||||
Notification_Preferences: 'Bildirim Tercihleri',
|
||||
No_available_agents_to_transfer: 'Aktarılacak yemsilci yok',
|
||||
Offline: 'Çevrimdışı',
|
||||
Oops: 'Ahh!',
|
||||
Omnichannel: 'Çoklu Kanal',
|
||||
Open_Livechats: 'Devam Eden Sohbetler',
|
||||
Omnichannel_enable_alert: 'Çoklu Kanal\'da mevcut değilsiniz. Erişilebilir olmak ister misiniz?',
|
||||
Onboarding_description: 'Çalışma alanı, ekibinizin veya kuruluşunuzun işbirliği alanıdır. Çalışma alanı yöneticisinden bir ekibe katılmak veya bir ekip oluşturmak için yardım isteyin.',
|
||||
Onboarding_join_workspace: 'Bir çalışma alanına katılın',
|
||||
Onboarding_subtitle: 'Ekip İşbirliğinin Ötesinde',
|
||||
Onboarding_title: 'Rocket.Chat\'e hoş geldiniz',
|
||||
Onboarding_join_open_description: 'Rocket.Chat ekibi ve topluluğu ile sohbet etmek için açık çalışma alanımıza katılın.',
|
||||
Onboarding_agree_terms: 'Devam ederek Rocket.Chat\'i kabul etmiş olursunuz',
|
||||
Onboarding_less_options: 'Daha az seçenek',
|
||||
Onboarding_more_options: 'Daha çok seçenek',
|
||||
Online: 'Çevrimiçi',
|
||||
Only_authorized_users_can_write_new_messages: 'Yalnızca yetkili kullanıcılar yeni ileti yazabilir',
|
||||
Open_emoji_selector: 'Emoji seçiciyi aç',
|
||||
Open_Source_Communication: 'Açık Kaynak İletişim',
|
||||
Open_your_authentication_app_and_enter_the_code: 'Kimlik doğrulama uygulamanızı açın ve kodu girin.',
|
||||
OR: 'OR',
|
||||
OS: 'OS',
|
||||
Overwrites_the_server_configuration_and_use_room_config: 'Sunucu yapılandırmasının üzerine yazar ve oda yapılandırmasını kullanır',
|
||||
Password: 'Şifre',
|
||||
Parent_channel_or_group: 'Üst kanal veya grup',
|
||||
Permalink_copied_to_clipboard: 'Kalıcı bağlantı panoya kopyalandı!',
|
||||
Phone: 'Telefon',
|
||||
Pin: 'Sabitle',
|
||||
Pinned_Messages: 'Sabitlenen İletiler',
|
||||
pinned: 'sabitlendi',
|
||||
Pinned: 'Sabitlendi',
|
||||
Please_add_a_comment: 'Lütfen bir yorum ekleyin',
|
||||
Please_enter_your_password: 'Lütfen şifrenizi giriniz',
|
||||
Please_wait: 'Lütfen bekle.',
|
||||
Preferences: 'Tercihler',
|
||||
Preferences_saved: 'Tercihler kaydedildi!',
|
||||
Privacy_Policy: ' Privacy Policy',
|
||||
Private_Channel: 'Özel Kanal',
|
||||
Private_Groups: 'Özel Gruplar',
|
||||
Private: 'Özel',
|
||||
Processing: 'İşleniyor...',
|
||||
Profile_saved_successfully: 'Profil başarıyla kaydedildi!',
|
||||
Profile: 'Profil',
|
||||
Public_Channel: 'Herkese Açık Kanal',
|
||||
Public: 'Herkese Açık',
|
||||
Push_Notifications: 'Anlık Bildirimler',
|
||||
Push_Notifications_Alert_Info: 'Bu bildirimler, uygulama açık olmadığında size teslim edilir',
|
||||
Quote: 'Alıntı',
|
||||
Reactions_are_disabled: 'Tepkiler devre dışı bırakıldı',
|
||||
Reactions_are_enabled: 'Tepkiler etkinleştirildi',
|
||||
Reactions: 'Tepkiler',
|
||||
Read: 'Oku',
|
||||
Read_External_Permission_Message: 'Rocket.Chat\'in cihazınızdaki fotoğraflara, medyaya ve dosyalara erişmesi gerekiyor',
|
||||
Read_External_Permission: 'Medya Okuma İzni ',
|
||||
Read_Only_Channel: 'Yazma Kısıtlı Kanal',
|
||||
Read_Only: 'Yazma Kısıtlı',
|
||||
Read_Receipt: 'Okundu Bilgisi',
|
||||
Receive_Group_Mentions: 'Grup Bahsetmelerini Al',
|
||||
Receive_Group_Mentions_Info: 'Grup bahsetmelerini al',
|
||||
Register: 'Kayıt Ol',
|
||||
Repeat_Password: 'Şifre Tekrarı',
|
||||
Replied_on: 'Yanıtlandı:',
|
||||
replies: 'yanıtlar',
|
||||
reply: 'yanıtla',
|
||||
Reply: 'Yanıtla',
|
||||
Report: 'Bildir',
|
||||
Receive_Notification: 'Bildirim Al',
|
||||
Receive_notifications_from: '{{name}} bildirimlerini alın',
|
||||
Resend: 'Yeniden yolla',
|
||||
Reset_password: 'Şifre sıfırla',
|
||||
resetting_password: 'şifre sıfırlanıyor',
|
||||
RESET: 'SIFIRLA',
|
||||
Return: 'Geri dön',
|
||||
Review_app_title: 'Uygulama hoşunuza gitti mi?',
|
||||
Review_app_desc: '{{store}} üzerinde bize 5 yıldız verin',
|
||||
Review_app_yes: 'Elbette!',
|
||||
Review_app_no: 'Hayır',
|
||||
Review_app_later: 'Belki daha sonra',
|
||||
Review_app_unable_store: '{{store}} açılamıyor!',
|
||||
Review_this_app: 'Bu uygulamayı değerlendirin',
|
||||
Remove: 'Kaldır',
|
||||
Roles: 'Roller',
|
||||
Room_actions: 'Oda işlemleri',
|
||||
Room_changed_announcement: 'Oda duyurusu, {{userBy}} tarafından {{announcement}} olarak değiştirildi',
|
||||
Room_changed_avatar: 'Oda profil fotoğrafı {{userBy}} tarafından değiştirildi',
|
||||
Room_changed_description: 'Oda açıklaması, {{userBy}} tarafından {{description}} olarak değiştirildi',
|
||||
Room_changed_privacy: 'Oda açıklaması, {{userBy}} tarafından {{description}} olarak değiştirildi',
|
||||
Room_changed_topic: 'Oda konusu, {{userBy}} tarafından {{topic}} olarak değiştirildi',
|
||||
Room_Files: 'Oda Dosyaları',
|
||||
Room_Info_Edit: 'Oda Bilgilerini Düzenle',
|
||||
Room_Info: 'Oda Bilgisi',
|
||||
Room_Members: 'Oda Üyeleri',
|
||||
Room_name_changed: 'Oda adı, {{userBy}} tarafından {{name}} olarak değiştirildi',
|
||||
SAVE: 'KAYDET',
|
||||
Save_Changes: 'Değişiklikleri Kaydet',
|
||||
Save: 'Kaydet',
|
||||
Saved: 'Kaydedildi',
|
||||
saving_preferences: 'tercihler kaydediliyor',
|
||||
saving_profile: 'profil kaydediliyor',
|
||||
saving_settings: 'ayarlar kaydediliyor',
|
||||
saved_to_gallery: 'Galeriye kaydedildi',
|
||||
Save_Your_E2E_Password: '(E2E) Şifrenizi Kaydedin',
|
||||
Save_Your_Encryption_Password: 'Şifreleme Parolanızı Kaydedin',
|
||||
Save_Your_Encryption_Password_warning: 'Bu parola hiçbir yerde saklanmadığından başka bir yere dikkatlice kaydedin.',
|
||||
Save_Your_Encryption_Password_info: 'Parolanızı kaybettiğinizde, kurtarmanın bir yolu olmadığını ve iletilerinize erişiminizi kaybedeceğinizi unutmayın.',
|
||||
Search_Messages: 'İleti ara',
|
||||
Search: 'Ara',
|
||||
Search_by: 'Ara',
|
||||
Search_global_users: 'Global kullanıcıları ara',
|
||||
Search_global_users_description: 'Açarsanız, diğer şirketlerden veya sunuculardan herhangi bir kullanıcıyı arayabilirsiniz.',
|
||||
Seconds: '{{second}} saniye',
|
||||
Security_and_privacy: 'Güvenlik ve gizlilik',
|
||||
Select_Avatar: 'Profil resmi seç',
|
||||
Select_Server: 'Sunucu seç',
|
||||
Select_Users: 'Kullanıcıları seç',
|
||||
Select_a_Channel: 'Kanal Seç',
|
||||
Select_a_Department: 'Bölüm Seç',
|
||||
Select_an_option: 'Bir seçenek seçin',
|
||||
Select_a_User: 'Kullanıcı Seç',
|
||||
Send: 'Yolla',
|
||||
Send_audio_message: 'Sesli ileti gönder',
|
||||
Send_crash_report: 'Çökme raporu gönder',
|
||||
Send_message: 'İleti gönder',
|
||||
Send_me_the_code_again: 'Kodu tekrar gönder',
|
||||
Send_to: 'Gönderiliyor...',
|
||||
Sending_to: 'Gönderiliyor:',
|
||||
Sent_an_attachment: 'Bir ek gönderildi',
|
||||
Server: 'Sunucu',
|
||||
Servers: 'Sunucular',
|
||||
Server_version: 'Sunucu versiyonu: {{version}}',
|
||||
Set_username_subtitle: 'Kullanıcı adı başkalarının iletilerde sizden bahsetmesine izin vermek için kullanılır',
|
||||
Set_custom_status: 'Özelleştirilmiş durumu ayarlayın',
|
||||
Set_status: 'Durumu ayarla',
|
||||
Status_saved_successfully: 'Durum başarıyla kaydedildi!',
|
||||
Settings: 'Ayarlar',
|
||||
Settings_succesfully_changed: 'Ayarlar başarıyla değiştirildi!',
|
||||
Share: 'Paylaş',
|
||||
Share_Link: 'Bağlantı paylaş',
|
||||
Share_this_app: 'Bu uygulamayı paylaş',
|
||||
Show_more: 'Daha fazla göster..',
|
||||
Show_Unread_Counter: 'Okunmamış Sayacını Göster',
|
||||
Show_Unread_Counter_Info: 'Okunmamış sayacı, listede kanalın sağ tarafında bir rozet olarak görüntülenir',
|
||||
Sign_in_your_server: 'Sunucunuzda oturum açın',
|
||||
Sign_Up: 'Kaydol',
|
||||
Some_field_is_invalid_or_empty: 'Bazı alanlar geçersiz veya boş',
|
||||
Sorting_by: '{{key}} göre sıralanıyor',
|
||||
Sound: 'Ses',
|
||||
Star_room: 'Odayı Yıldızla',
|
||||
Star: 'Yıldızla',
|
||||
Starred_Messages: 'Yıldızlı İletiler',
|
||||
starred: 'yıldızlandı',
|
||||
Starred: 'Yıldızlandı',
|
||||
Start_of_conversation: 'Konuşma başlangıcı',
|
||||
Start_a_Discussion: 'Tartışma Başlat',
|
||||
Started_discussion: 'Bir tartışma başlattı:',
|
||||
Started_call: 'Arama {{userBy}} tarafından başlatıldı',
|
||||
Submit: 'Kaydet',
|
||||
Table: 'Tablo',
|
||||
Tags: 'Etiketler',
|
||||
Take_a_photo: 'Fotoğraf çek',
|
||||
Take_a_video: 'Video çek',
|
||||
Take_it: 'Al!',
|
||||
tap_to_change_status: 'durumu değiştirmek için dokunun',
|
||||
Tap_to_view_servers_list: 'Sunucu listesini görüntülemek için dokunun',
|
||||
Terms_of_Service: ' Kullanım Şartları ',
|
||||
Theme: 'Tema',
|
||||
The_user_wont_be_able_to_type_in_roomName: 'Kullanıcı {{roomName}} içinde yazamayacak',
|
||||
The_user_will_be_able_to_type_in_roomName: 'Kullanıcı {{roomName}} içinde yazabilecek',
|
||||
There_was_an_error_while_action: '{{action}} sırasında bir hata oluştu!',
|
||||
This_room_is_blocked: 'Bu oda engellendi',
|
||||
This_room_is_read_only: 'Bu oda yazma kısıtlı',
|
||||
Thread: 'Başlık',
|
||||
Threads: 'Başlıklar',
|
||||
Timezone: 'Saat dilimi',
|
||||
To: 'Kime',
|
||||
topic: 'konu',
|
||||
Topic: 'Konu',
|
||||
Translate: 'Çevir',
|
||||
Try_again: 'Tekrar deneyin',
|
||||
Two_Factor_Authentication: 'İki faktörlü Kimlik Doğrulama',
|
||||
Type_the_channel_name_here: 'Kanal adını buraya yazın',
|
||||
unarchive: 'arşivden çıkar',
|
||||
UNARCHIVE: 'ARŞİVDEN ÇIKAR',
|
||||
Unblock_user: 'Kullanıcının engelini kaldır',
|
||||
Unfavorite: 'Favorilerden Çıkar',
|
||||
Unfollowed_thread: 'Takip edilmeyen başlık',
|
||||
Unmute: 'Sesi Aç',
|
||||
unmuted: 'Sesi Açıldı',
|
||||
Unpin: 'Sabitlemeyi kaldır',
|
||||
unread_messages: 'okunmamış',
|
||||
Unread: 'Okunmamış',
|
||||
Unread_on_top: 'Okunmamışlar üstte',
|
||||
Unstar: 'Yıldızı kaldır',
|
||||
Updating: 'Güncelleniyor...',
|
||||
Uploading: 'Gönderiliyor',
|
||||
Upload_file_question_mark: 'Dosya gönderilsin mi?',
|
||||
User: 'Kullanıcı',
|
||||
Users: 'Kullanıcılar',
|
||||
User_added_by: '{{userAdded}} adlı kullanıcı {{userBy}} tarafından eklendi',
|
||||
User_Info: 'Kullanıcı bilgisi',
|
||||
User_has_been_key: 'Kullanıcı {{key}} olmuştur',
|
||||
User_is_no_longer_role_by_: '{{user}} artık {{role}} değil ({{userBy}} tarafından)',
|
||||
User_muted_by: '{{userMuted}} adlı kullanıcının sesini {{userBy}} kapattı',
|
||||
User_removed_by: '{{userRemoved}} kullanıcısı {{userBy}} tarafından kaldırıldı',
|
||||
User_sent_an_attachment: '{{user}} bir ek gönderdi',
|
||||
User_unmuted_by: '{{userUnmuted}} kullanıcısının sesi {{userBy}} tarafından açıldı',
|
||||
User_was_set_role_by_: '{{user}}, {{userBy}} tarafından {{role}} ayarlandı',
|
||||
Username_is_empty: 'Kullanıcı adı boş!',
|
||||
Username: 'Kullanıcı adı',
|
||||
Username_or_email: 'Kullanıcı adı ya da e-posta',
|
||||
Uses_server_configuration: 'Sunucu yapılandırmasını kullanır',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Genellikle tartışma, "Nasıl resim yüklerim?" gibi bir soruyla başlar.',
|
||||
Validating: 'Doğrulanıyor',
|
||||
Registration_Succeeded: 'Kayıt Başarılı!',
|
||||
Verify: 'Onayla',
|
||||
Verify_email_title: 'Kaydınızı onaylayın!',
|
||||
Verify_email_desc: 'Kaydınızı onaylamak için size bir e-posta gönderdik. Kısa süre içinde bir e-posta almazsanız, lütfen geri gelin ve tekrar deneyin.',
|
||||
Verify_your_email_for_the_code_we_sent: 'Gönderdiğimiz kod için e-postanızı doğrulayın',
|
||||
Video_call: 'Görüntülü arama',
|
||||
View_Original: 'Orijinali Görüntüle',
|
||||
Voice_call: 'Sesli arama',
|
||||
Waiting_for_network: 'Ağ bağlantısı bekleniyor ...',
|
||||
Websocket_disabled: 'Bu sunucu için Websocket devre dışı bırakıldı.\n{{contact}}',
|
||||
Welcome: 'Hoşgeldiniz',
|
||||
What_are_you_doing_right_now: 'Şu an ne yapıyorsun?',
|
||||
Whats_your_2fa: '2 faktör doğrulama (2FA) kodunuz nedir?',
|
||||
Without_Servers: 'Sunucusuz',
|
||||
Workspaces: 'Çalışma alanları',
|
||||
Would_you_like_to_return_the_inquiry: 'Başvuruyu geri çevirmek ister misiniz?',
|
||||
Write_External_Permission_Message: 'Rocket.Chat\'in galerinize erişmesi gerekiyor, böylece resimleri kaydedebilirsiniz.',
|
||||
Write_External_Permission: 'Galeri İzni',
|
||||
Yes: 'Evet',
|
||||
Yes_action_it: 'Evet, {{action}}!',
|
||||
Yesterday: 'Dün',
|
||||
You_are_in_preview_mode: 'İzleme modundasınız',
|
||||
You_are_offline: 'Çevrimdışısınız',
|
||||
You_can_search_using_RegExp_eg: 'Düzenli ifadeleri (Regular Expressions) kullanabilirsiniz. Örneğin: `/^text$/i`',
|
||||
You_colon: 'Siz: ',
|
||||
you_were_mentioned: 'senden bahsedildi',
|
||||
You_were_removed_from_channel: '{{channel}} kanalından çıkarıldınız',
|
||||
you: 'siz',
|
||||
You: 'Siz',
|
||||
Logged_out_by_server: 'Sunucu tarafından çıkış yaptınız. Lütfen tekrar giriş yapın.',
|
||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Bir şeyler paylaşmak için Rocket.Chat sunucusuna erişmeniz gerekir.',
|
||||
You_need_to_verifiy_your_email_address_to_get_notications: 'Bildirim almak için e-posta adresinizi doğrulamanız gerekiyor',
|
||||
Your_certificate: 'Sertifikanız',
|
||||
Your_message: 'İletiınız',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Davet bağlantınızın geçerliliği {{usesLeft}} kullanımdan sonra sona erecek.',
|
||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Davet bağlantınızın geçerliliği {{date}} tarihinde veya {{usesLeft}} kullanımdan sonra sona erecek.',
|
||||
Your_invite_link_will_expire_on__date__: 'Davet bağlantınızın geçerlilik süresi {{date}} tarihinde sona erecek.',
|
||||
Your_invite_link_will_never_expire: 'Davet bağlantınızın geçerlilik süresi asla dolmayacak.',
|
||||
Your_workspace: 'Çalışma alanınız',
|
||||
Your_password_is: 'Şifreniz',
|
||||
Version_no: 'Versiyon: {{version}}',
|
||||
You_will_not_be_able_to_recover_this_message: 'Bu iletiyi kurtaramayacaksınız!',
|
||||
You_will_unset_a_certificate_for_this_server: 'Bu sunucu için bir sertifika ayarını kaldıracaksınız',
|
||||
Change_Language: 'Dili değiştir',
|
||||
Crash_report_disclaimer: 'Sohbetlerinizin içeriğini asla takip etmiyoruz. Çökme raporu ve analiz olayları, sorunları tanımlamak ve düzeltmek için yalnızca bizim için ilgili bilgileri içerir.',
|
||||
Type_message: 'İleti yaz',
|
||||
Room_search: 'Oda arama',
|
||||
Room_selection: 'Oda seçimi 1...9',
|
||||
Next_room: 'Sonraki oda',
|
||||
Previous_room: 'Önceki oda',
|
||||
New_room: 'Yeni oda',
|
||||
Upload_room: 'Odaya yükle',
|
||||
Search_messages: 'İletilerda ara',
|
||||
Scroll_messages: 'İletilerı kaydır',
|
||||
Reply_latest: 'Sonuncuyu yanıtla',
|
||||
Reply_in_Thread: 'Konu içinde cevapla',
|
||||
Server_selection: 'Sunucu seçimi',
|
||||
Server_selection_numbers: 'Sunucu seçimi 1...9',
|
||||
Add_server: 'Sunucu ekle',
|
||||
New_line: 'Yeni satır',
|
||||
You_will_be_logged_out_of_this_application: 'Bu uygulamadan çıkış yapacaksınız.',
|
||||
Clear: 'Temizle',
|
||||
This_will_clear_all_your_offline_data: 'Bu, tüm çevrimdışı verilerinizi temizleyecektir.',
|
||||
This_will_remove_all_data_from_this_server: 'Bu, bu sunucudaki tüm verileri kaldıracaktır.',
|
||||
Mark_unread: 'Okunmadı olarak işaretle',
|
||||
Wait_activation_warning: 'Giriş yapmadan önce, hesabınız bir yönetici tarafından manuel olarak etkinleştirilmelidir.',
|
||||
Screen_lock: 'Ekran kilidi',
|
||||
Local_authentication_biometry_title: 'Doğrula',
|
||||
Local_authentication_biometry_fallback: 'Parola kullan',
|
||||
Local_authentication_unlock_option: 'Şifre ile Kilidi Açın',
|
||||
Local_authentication_change_passcode: 'Parolayı Değiştir',
|
||||
Local_authentication_info: 'Not: Parolayı unutursanız, uygulamayı silmeniz ve yeniden yüklemeniz gerekir.',
|
||||
Local_authentication_facial_recognition: 'yüz tanıma',
|
||||
Local_authentication_fingerprint: 'parmak izi',
|
||||
Local_authentication_unlock_with_label: '{{label}} ile kilidi açın',
|
||||
Local_authentication_auto_lock_60: '1 dakika sonra',
|
||||
Local_authentication_auto_lock_300: '5 dakika sonra',
|
||||
Local_authentication_auto_lock_900: '15 dakika sonra',
|
||||
Local_authentication_auto_lock_1800: '30 dakika sonra',
|
||||
Local_authentication_auto_lock_3600: '1 saat sonra',
|
||||
Passcode_enter_title: 'Şifrenizi giriniz',
|
||||
Passcode_choose_title: 'Yeni şifrenizi yazın',
|
||||
Passcode_choose_confirm_title: 'Yeni şifrenizi onaylayın',
|
||||
Passcode_choose_error: 'Parolalar eşleşmiyor. Tekrar deneyin.',
|
||||
Passcode_choose_force_set: 'Yönetici tarafından istenen şifre',
|
||||
Passcode_app_locked_title: 'Uygulama kilitlendi',
|
||||
Passcode_app_locked_subtitle: '{{timeLeft}} saniye içinde tekrar deneyin',
|
||||
After_seconds_set_by_admin: '{{seconds}} saniye sonra (yönetici tarafından belirlenir)',
|
||||
Dont_activate: 'Şimdi etkinleştirme',
|
||||
Queued_chats: 'Sıralı sohbetler',
|
||||
Queue_is_empty: 'Sıra boş',
|
||||
Logout_from_other_logged_in_locations: 'Giriş yapılan diğer konumlardan çıkış yap',
|
||||
You_will_be_logged_out_from_other_locations: 'Diğer konumlardan çıkış yapacaksınız.',
|
||||
Logged_out_of_other_clients_successfully: 'Diğer istemcilerden başarıyla çıkış yapıldı',
|
||||
Logout_failed: 'Oturum kapatma başarısız oldu!',
|
||||
Log_analytics_events: 'Olayları günlüğe kaydet',
|
||||
E2E_encryption_change_password_title: 'Şifreleme Parolasını Değiştir',
|
||||
E2E_encryption_change_password_description: 'Artık şifrelenmiş özel gruplar ve doğrudan iletiler oluşturabilirsiniz. Mevcut özel grupları veya özel iletileri şifreli olarak da değiştirebilirsiniz. \nBu uçtan uca şifrelemedir, bu nedenle iletilerinizi şifrelemek / çözmek için kullanılan anahtar sunucuya kaydedilmez. Bu nedenle şifrenizi güvenli bir yerde saklamanız gerekir. Uçtan uca şifrelemeyi (E2E) kullanmak istediğiniz diğer cihazlara girmeniz istenecektir.',
|
||||
E2E_encryption_change_password_error: 'Uçtan uca şifreleme (E2E) anahtar şifresi değiştirilirken hata!',
|
||||
E2E_encryption_change_password_success: 'Uçtan uca şifreleme (E2E) anahtar şifresi başarıyla değiştirildi!',
|
||||
E2E_encryption_change_password_message: 'Dikkatlice kaydettiğinizden emin olun.',
|
||||
E2E_encryption_change_password_confirmation: 'Evet, değiştir!',
|
||||
E2E_encryption_reset_title: 'Uçtan uca şifreleme (E2E) Anahtarını Sıfırla',
|
||||
E2E_encryption_reset_description: 'Bu seçenek mevcut uçtan uca şifreleme (E2E) anahtarınızı kaldıracak ve oturumu kapatacaktır. \nTekrar oturum açtığınızda, Rocket.Chat size yeni bir anahtar oluşturacak ve çevrimiçi olarak bir veya daha fazla üyesi olan şifreli herhangi bir odaya erişiminizi geri yükleyecektir. \nUçtan uca şifrelemenin (E2E) doğası gereği Rocket.Chat, çevrimiçi üyesi olmayan hiçbir şifreli odaya erişimi geri yükleyemez.',
|
||||
E2E_encryption_reset_button: 'Uçtan uca şifreleme (E2E) anahtarını sıfırla',
|
||||
E2E_encryption_reset_error: 'Uçtan uca şifreleme (E2E) anahtarı sıfırlanırken hata!',
|
||||
E2E_encryption_reset_message: 'Oturumunuz kapatılacak!',
|
||||
E2E_encryption_reset_confirmation: 'Evet, sıfırla',
|
||||
Following: 'Takip ediliyor',
|
||||
Threads_displaying_all: 'Tüm konular görüntüleniyor',
|
||||
Threads_displaying_following: 'Takip edilen konular görüntüleniyor',
|
||||
Threads_displaying_unread: 'Okunmamış konular görüntüleniyor',
|
||||
No_threads: 'Konu yok',
|
||||
No_threads_following: 'Herhangi bir konuyu takip etmiyorsunuz',
|
||||
No_threads_unread: 'Okunmamış konu yok',
|
||||
Messagebox_Send_to_channel: 'Kanala gönder',
|
||||
Set_as_leader: 'Lider olarak ayarla',
|
||||
Set_as_moderator: 'Moderatör olarak ayarla',
|
||||
Set_as_owner: 'Sahip olarak ayarla',
|
||||
Remove_as_leader: 'Lider olarak kaldır',
|
||||
Remove_as_moderator: 'Moderatör olarak kaldır',
|
||||
Remove_as_owner: 'Sahip olarak kaldır',
|
||||
Remove_from_room: 'Odadan çıkar',
|
||||
Ignore: 'Yok say',
|
||||
Unignore: 'Yok sayma',
|
||||
User_has_been_ignored: 'Kullanıcı yok sayıldı.',
|
||||
User_has_been_unignored: 'Kullanıcı artık yok sayılmıyor.',
|
||||
User_has_been_removed_from_s: 'Kullanıcı {{s}} alanından kaldırıldı.',
|
||||
User__username__is_now_a_leader_of__room_name_: '{{Username}} kullanıcısı artık {{room_name}} lideridir.',
|
||||
User__username__is_now_a_moderator_of__room_name_: '{{Username}} kullanıcısı artık bir {{room_name}} moderatörüdür.',
|
||||
User__username__is_now_a_owner_of__room_name_: '{{Username}} kullanıcısı artık {{room_name}} adlı odanın sahibidir.',
|
||||
User__username__removed_from__room_name__leaders: '{{Username}} adlı kullanıcı, {{room_name}} liderlerinden kaldırıldı.',
|
||||
User__username__removed_from__room_name__moderators: '{{Username}} adlı kullanıcı, {{room_name}} moderatörlerinden kaldırıldı.',
|
||||
User__username__removed_from__room_name__owners: '{{Username}} adlı kullanıcı, {{room_name}} sahiplerinden kaldırıldı.',
|
||||
The_user_will_be_removed_from_s: 'Kullanıcı, {{s}} alanından kaldırılacak!',
|
||||
Yes_remove_user: 'Evet, kullanıcıyı kaldır!',
|
||||
Direct_message: 'Özel ileti',
|
||||
Message_Ignored: 'İleti yok sayıldı. Görüntülemek için dokunun.',
|
||||
Enter_workspace_URL: 'Çalışma alanı URL\'nizi girin',
|
||||
Workspace_URL_Example: 'Örn. sirketiniz.rocket.chat'
|
||||
};
|
|
@ -563,7 +563,6 @@ export default {
|
|||
Username: '用户名',
|
||||
Username_or_email: '用户名或邮箱',
|
||||
Uses_server_configuration: '使用服务器设置',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: '通常, 一个讨论会由一个问题开始, 例如 \\"如何上传图片?\\"',
|
||||
Validating: '正在验证',
|
||||
Registration_Succeeded: '注册成功',
|
||||
Verify: '验证',
|
||||
|
@ -598,7 +597,6 @@ export default {
|
|||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: '您需要访问至少一台Rocket.Chat服务器才能共享某些内容。',
|
||||
You_need_to_verifiy_your_email_address_to_get_notications: '您需要先验证您的邮箱以启用通知',
|
||||
Your_certificate: '你的证书',
|
||||
Your_message: '你的信息',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses: '您的邀请链接将在{{usesLeft}}使用后到期。',
|
||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: '您的邀请链接将于{{date}}或{{usesLeft}}使用后到期。',
|
||||
Your_invite_link_will_expire_on__date__: '您的邀请链接将于{{date}}到期。',
|
||||
|
|
|
@ -563,7 +563,6 @@ export default {
|
|||
Username: '使用者名稱',
|
||||
Username_or_email: '使用者名稱或電子郵件',
|
||||
Uses_server_configuration: '使用伺服器設定',
|
||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: '通常,討論會由一個問題開始,像是 \\\'如何上傳一個圖片?\\\'',
|
||||
Validating: '正在驗證',
|
||||
Registration_Succeeded: '註冊成功',
|
||||
Verify: '驗證',
|
||||
|
@ -598,7 +597,6 @@ export default {
|
|||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: '您需要至少連接一個 Rocket.Chat 伺服器才能共享某些内容。',
|
||||
You_need_to_verifiy_your_email_address_to_get_notications: '您需要先驗證您的電子郵件以啟用通知',
|
||||
Your_certificate: '你的證書',
|
||||
Your_message: '你的訊息',
|
||||
Your_invite_link_will_expire_after__usesLeft__uses: '您的邀請連結將在{{usesLeft}}使用後到期。',
|
||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: '您的邀請連結將於{{date}}或{{usesLeft}}使用後到期。',
|
||||
Your_invite_link_will_expire_on__date__: '您的邀請連結將於{{date}}到期。',
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -68,10 +68,6 @@ class Encryption {
|
|||
return this.readyPromise;
|
||||
}
|
||||
|
||||
get hasPrivateKey() {
|
||||
return !!this.privateKey;
|
||||
}
|
||||
|
||||
// Stop Encryption client
|
||||
stop = () => {
|
||||
this.userId = null;
|
||||
|
|
|
@ -596,25 +596,19 @@ const RocketChat = {
|
|||
readMessages,
|
||||
resendMessage,
|
||||
|
||||
async search({ text, filterUsers = true, filterRooms = true }) {
|
||||
async localSearch({ text, filterUsers = true, filterRooms = true }) {
|
||||
const searchText = text.trim();
|
||||
|
||||
if (this.oldPromise) {
|
||||
this.oldPromise('cancel');
|
||||
}
|
||||
|
||||
if (searchText === '') {
|
||||
delete this.oldPromise;
|
||||
return [];
|
||||
}
|
||||
|
||||
const db = database.active;
|
||||
const likeString = sanitizeLikeString(searchText);
|
||||
let data = await db.collections.get('subscriptions').query(
|
||||
Q.or(
|
||||
Q.where('name', Q.like(`%${ likeString }%`)),
|
||||
Q.where('fname', Q.like(`%${ likeString }%`))
|
||||
)
|
||||
),
|
||||
Q.experimentalSortBy('room_updated_at', Q.desc)
|
||||
).fetch();
|
||||
|
||||
if (filterUsers && !filterRooms) {
|
||||
|
@ -627,18 +621,34 @@ const RocketChat = {
|
|||
|
||||
data = data.map((sub) => {
|
||||
if (sub.t !== 'd') {
|
||||
return ({
|
||||
return {
|
||||
rid: sub.rid,
|
||||
name: sub.name,
|
||||
fname: sub.fname,
|
||||
avatarETag: sub.avatarETag,
|
||||
t: sub.t,
|
||||
search: true
|
||||
});
|
||||
encrypted: sub.encrypted
|
||||
};
|
||||
}
|
||||
return sub;
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
async search({ text, filterUsers = true, filterRooms = true }) {
|
||||
const searchText = text.trim();
|
||||
|
||||
if (this.oldPromise) {
|
||||
this.oldPromise('cancel');
|
||||
}
|
||||
|
||||
if (searchText === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data = await this.localSearch({ text, filterUsers, filterRooms });
|
||||
|
||||
const usernames = data.map(sub => sub.name);
|
||||
try {
|
||||
if (data.length < 7) {
|
||||
|
@ -647,13 +657,18 @@ const RocketChat = {
|
|||
new Promise((resolve, reject) => this.oldPromise = reject)
|
||||
]);
|
||||
if (filterUsers) {
|
||||
data = data.concat(users.map(user => ({
|
||||
...user,
|
||||
rid: user.username,
|
||||
name: user.username,
|
||||
t: 'd',
|
||||
search: true
|
||||
})));
|
||||
users
|
||||
.filter((item1, index) => users.findIndex(item2 => item2._id === item1._id) === index) // Remove duplicated data from response
|
||||
.filter(user => !data.some(sub => user.username === sub.name)) // Make sure to remove users already on local database
|
||||
.forEach((user) => {
|
||||
data.push({
|
||||
...user,
|
||||
rid: user.username,
|
||||
name: user.username,
|
||||
t: 'd',
|
||||
search: true
|
||||
});
|
||||
});
|
||||
}
|
||||
if (filterRooms) {
|
||||
rooms.forEach((room) => {
|
||||
|
@ -697,11 +712,11 @@ const RocketChat = {
|
|||
},
|
||||
|
||||
createDiscussion({
|
||||
prid, pmid, t_name, reply, users
|
||||
prid, pmid, t_name, reply, users, encrypted
|
||||
}) {
|
||||
// RC 1.0.0
|
||||
return this.post('rooms.createDiscussion', {
|
||||
prid, pmid, t_name, reply, users
|
||||
prid, pmid, t_name, reply, users, encrypted
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -865,14 +880,7 @@ const RocketChat = {
|
|||
methodCallWrapper(method, ...params) {
|
||||
const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings;
|
||||
if (API_Use_REST_For_DDP_Calls) {
|
||||
return new Promise(async(resolve, reject) => {
|
||||
const data = await this.post(`method.call/${ method }`, { message: JSON.stringify({ method, params }) });
|
||||
const response = JSON.parse(data.message);
|
||||
if (response?.error) {
|
||||
return reject(response.error);
|
||||
}
|
||||
return resolve(response.result);
|
||||
});
|
||||
return this.post(`method.call/${ method }`, { message: JSON.stringify({ method, params }) });
|
||||
}
|
||||
return this.methodCall(method, ...params);
|
||||
},
|
||||
|
@ -1067,14 +1075,30 @@ const RocketChat = {
|
|||
},
|
||||
post(...args) {
|
||||
return new Promise(async(resolve, reject) => {
|
||||
const isMethodCall = args[0]?.startsWith('method.call/');
|
||||
try {
|
||||
const result = await this.sdk.post(...args);
|
||||
|
||||
/**
|
||||
* if API_Use_REST_For_DDP_Calls is enabled and it's a method call,
|
||||
* responses have a different object structure
|
||||
*/
|
||||
if (isMethodCall) {
|
||||
const response = JSON.parse(result.message);
|
||||
if (response?.error) {
|
||||
throw response.error;
|
||||
}
|
||||
return resolve(response.result);
|
||||
}
|
||||
return resolve(result);
|
||||
} catch (e) {
|
||||
if (e.data && (e.data.errorType === 'totp-required' || e.data.errorType === 'totp-invalid')) {
|
||||
const { details } = e.data;
|
||||
const errorType = isMethodCall ? e?.error : e?.data?.errorType;
|
||||
const totpInvalid = 'totp-invalid';
|
||||
const totpRequired = 'totp-required';
|
||||
if ([totpInvalid, totpRequired].includes(errorType)) {
|
||||
const { details } = isMethodCall ? e : e?.data;
|
||||
try {
|
||||
await twoFactor({ method: details?.method, invalid: e.data.errorType === 'totp-invalid' });
|
||||
await twoFactor({ method: details?.method, invalid: errorType === totpInvalid });
|
||||
return resolve(this.post(...args));
|
||||
} catch {
|
||||
// twoFactor was canceled
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
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) {
|
||||
|
|
|
@ -48,7 +48,7 @@ class Touchable extends React.Component {
|
|||
rowState: 0 // 0: closed, 1: right opened, -1: left opened
|
||||
};
|
||||
this._onGestureEvent = Animated.event(
|
||||
[{ nativeEvent: { translationX: this.dragX } }]
|
||||
[{ nativeEvent: { translationX: this.dragX } }], { useNativeDriver: true }
|
||||
);
|
||||
this._value = 0;
|
||||
}
|
||||
|
|
|
@ -1,23 +1,33 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text } from 'react-native';
|
||||
import { View, Text, Pressable } from 'react-native';
|
||||
import FastImage from '@rocket.chat/react-native-fast-image';
|
||||
|
||||
import Touch from '../../utils/touch';
|
||||
import Check from '../../containers/Check';
|
||||
import styles, { ROW_HEIGHT } from './styles';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { isIOS } from '../../utils/deviceInfo';
|
||||
import { withTheme } from '../../theme';
|
||||
|
||||
export { ROW_HEIGHT };
|
||||
|
||||
const defaultLogo = require('../../static/images/logo.png');
|
||||
|
||||
const ServerItem = React.memo(({
|
||||
server, item, onPress, hasCheck, theme
|
||||
item, onPress, onLongPress, hasCheck, theme
|
||||
}) => (
|
||||
<Touch
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
style={[styles.serverItem, { backgroundColor: themes[theme].backgroundColor }]}
|
||||
onLongPress={() => onLongPress?.()}
|
||||
testID={`rooms-list-header-server-${ item.id }`}
|
||||
theme={theme}
|
||||
android_ripple={{
|
||||
color: themes[theme].bannerBackground
|
||||
}}
|
||||
style={({ pressed }) => ({
|
||||
backgroundColor: isIOS && pressed
|
||||
? themes[theme].bannerBackground
|
||||
: themes[theme].backgroundColor
|
||||
})}
|
||||
>
|
||||
<View style={styles.serverItemContainer}>
|
||||
{item.iconURL
|
||||
|
@ -27,33 +37,33 @@ const ServerItem = React.memo(({
|
|||
uri: item.iconURL,
|
||||
priority: FastImage.priority.high
|
||||
}}
|
||||
defaultSource={{ uri: 'logo' }}
|
||||
defaultSource={defaultLogo}
|
||||
style={styles.serverIcon}
|
||||
onError={() => console.log('err_loading_server_icon')}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<FastImage
|
||||
source={{ uri: 'logo' }}
|
||||
source={defaultLogo}
|
||||
style={styles.serverIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<View style={styles.serverTextContainer}>
|
||||
<Text style={[styles.serverName, { color: themes[theme].titleText }]}>{item.name || item.id}</Text>
|
||||
<Text style={[styles.serverUrl, { color: themes[theme].auxiliaryText }]}>{item.id}</Text>
|
||||
<Text numberOfLines={1} style={[styles.serverName, { color: themes[theme].titleText }]}>{item.name || item.id}</Text>
|
||||
<Text numberOfLines={1} style={[styles.serverUrl, { color: themes[theme].auxiliaryText }]}>{item.id}</Text>
|
||||
</View>
|
||||
{item.id === server && hasCheck ? <Check theme={theme} /> : null}
|
||||
{hasCheck ? <Check theme={theme} /> : null}
|
||||
</View>
|
||||
</Touch>
|
||||
</Pressable>
|
||||
));
|
||||
|
||||
ServerItem.propTypes = {
|
||||
onPress: PropTypes.func.isRequired,
|
||||
item: PropTypes.object.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
onLongPress: PropTypes.func,
|
||||
hasCheck: PropTypes.bool,
|
||||
server: PropTypes.string,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default ServerItem;
|
||||
export default withTheme(ServerItem);
|
||||
|
|
|
@ -5,10 +5,6 @@ import sharedStyles from '../../views/Styles';
|
|||
export const ROW_HEIGHT = 56;
|
||||
|
||||
export default StyleSheet.create({
|
||||
serverItem: {
|
||||
height: ROW_HEIGHT,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
serverItemContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
|
@ -16,20 +12,22 @@ export default StyleSheet.create({
|
|||
serverIcon: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
marginHorizontal: 15,
|
||||
borderRadius: 4
|
||||
margin: 12,
|
||||
borderRadius: 4,
|
||||
resizeMode: 'contain'
|
||||
},
|
||||
serverTextContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
justifyContent: 'center',
|
||||
paddingRight: 18
|
||||
},
|
||||
serverName: {
|
||||
fontSize: 18,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
serverUrl: {
|
||||
fontSize: 15,
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
import { ENCRYPTION } from '../actions/actionsTypes';
|
||||
|
||||
const initialState = {
|
||||
enabled: false,
|
||||
banner: null
|
||||
};
|
||||
|
||||
export default function encryption(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case ENCRYPTION.SET:
|
||||
return {
|
||||
...state,
|
||||
enabled: action.enabled,
|
||||
banner: action.banner
|
||||
};
|
||||
case ENCRYPTION.SET_BANNER:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -8,7 +8,8 @@ const initialState = {
|
|||
version: null,
|
||||
loading: true,
|
||||
adding: false,
|
||||
previousServer: null
|
||||
previousServer: null,
|
||||
changingServer: false
|
||||
};
|
||||
|
||||
|
||||
|
@ -34,7 +35,8 @@ export default function server(state = initialState, action) {
|
|||
version: action.version,
|
||||
connecting: true,
|
||||
connected: false,
|
||||
loading: true
|
||||
loading: true,
|
||||
changingServer: action.changeServer
|
||||
};
|
||||
case SERVER.SELECT_SUCCESS:
|
||||
return {
|
||||
|
@ -43,14 +45,16 @@ export default function server(state = initialState, action) {
|
|||
version: action.version,
|
||||
connecting: false,
|
||||
connected: true,
|
||||
loading: false
|
||||
loading: false,
|
||||
changingServer: false
|
||||
};
|
||||
case SERVER.SELECT_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
connecting: false,
|
||||
connected: false,
|
||||
loading: false
|
||||
loading: false,
|
||||
changingServer: false
|
||||
};
|
||||
case SERVER.INIT_ADD:
|
||||
return {
|
||||
|
|
|
@ -2,7 +2,7 @@ import EJSON from 'ejson';
|
|||
import { takeLatest, select, put } from 'redux-saga/effects';
|
||||
|
||||
import { ENCRYPTION } from '../actions/actionsTypes';
|
||||
import { encryptionSetBanner } from '../actions/encryption';
|
||||
import { encryptionSet } from '../actions/encryption';
|
||||
import { Encryption } from '../lib/encryption';
|
||||
import Navigation from '../lib/Navigation';
|
||||
import {
|
||||
|
@ -52,14 +52,14 @@ const handleEncryptionInit = function* handleEncryptionInit() {
|
|||
// A private key was received from the server, but it's not saved locally yet
|
||||
// Show the banner asking for the password
|
||||
if (!storedPrivateKey && keys?.privateKey) {
|
||||
yield put(encryptionSetBanner(E2E_BANNER_TYPE.REQUEST_PASSWORD));
|
||||
yield put(encryptionSet(false, E2E_BANNER_TYPE.REQUEST_PASSWORD));
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user has a private key stored, but never entered the password
|
||||
const storedRandomPassword = yield UserPreferences.getStringAsync(`${ server }-${ E2E_RANDOM_PASSWORD_KEY }`);
|
||||
if (storedRandomPassword) {
|
||||
yield put(encryptionSetBanner(E2E_BANNER_TYPE.SAVE_PASSWORD));
|
||||
yield put(encryptionSet(true, E2E_BANNER_TYPE.SAVE_PASSWORD));
|
||||
}
|
||||
|
||||
// Fetch stored public e2e key for this server
|
||||
|
@ -72,10 +72,11 @@ const handleEncryptionInit = function* handleEncryptionInit() {
|
|||
if (storedPublicKey && storedPrivateKey) {
|
||||
// Persist these keys
|
||||
yield Encryption.persistKeys(server, storedPublicKey, storedPrivateKey);
|
||||
yield put(encryptionSet(true));
|
||||
} else {
|
||||
// Create new keys since the user doesn't have any
|
||||
yield Encryption.createKeys(user.id, server);
|
||||
yield put(encryptionSetBanner(E2E_BANNER_TYPE.SAVE_PASSWORD));
|
||||
yield put(encryptionSet(true, E2E_BANNER_TYPE.SAVE_PASSWORD));
|
||||
}
|
||||
|
||||
// Decrypt all pending messages/subscriptions
|
||||
|
@ -87,7 +88,7 @@ const handleEncryptionInit = function* handleEncryptionInit() {
|
|||
|
||||
const handleEncryptionStop = function* handleEncryptionStop() {
|
||||
// Hide encryption banner
|
||||
yield put(encryptionSetBanner());
|
||||
yield put(encryptionSet());
|
||||
// Stop Encryption client
|
||||
Encryption.stop();
|
||||
};
|
||||
|
@ -112,7 +113,7 @@ const handleEncryptionDecodeKey = function* handleEncryptionDecodeKey({ password
|
|||
Encryption.initialize(user.id);
|
||||
|
||||
// Hide encryption banner
|
||||
yield put(encryptionSetBanner());
|
||||
yield put(encryptionSet(true));
|
||||
|
||||
Navigation.back();
|
||||
} catch {
|
||||
|
|
|
@ -34,7 +34,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;
|
||||
|
@ -123,32 +122,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());
|
||||
};
|
||||
|
||||
|
@ -160,7 +134,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);
|
||||
|
|
|
@ -61,10 +61,16 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
|
|||
const subsToCreate = subscriptions.filter(i1 => !existingSubs.find(i2 => i1._id === i2._id));
|
||||
const subsToDelete = existingSubs.filter(i1 => !subscriptions.find(i2 => i1._id === i2._id));
|
||||
|
||||
const openedRooms = yield select(state => state.room.rooms);
|
||||
const lastMessages = subscriptions
|
||||
/** Checks for opened rooms and filter them out.
|
||||
* It prevents this process to try persisting the same last message on the room messages fetch.
|
||||
* This race condition is easy to reproduce on push notification tap.
|
||||
*/
|
||||
.filter(sub => !openedRooms.includes(sub.rid))
|
||||
.map(sub => sub.lastMessage && buildMessage(sub.lastMessage))
|
||||
.filter(lm => lm);
|
||||
const lastMessagesIds = lastMessages.map(lm => lm._id);
|
||||
const lastMessagesIds = lastMessages.map(lm => lm._id).filter(lm => lm);
|
||||
const existingMessages = yield messagesCollection.query(Q.where('id', Q.oneOf(lastMessagesIds))).fetch();
|
||||
const messagesToUpdate = existingMessages.filter(i1 => lastMessages.find(i2 => i1.id === i2._id));
|
||||
const messagesToCreate = lastMessages.filter(i1 => !existingMessages.find(i2 => i1._id === i2.id));
|
||||
|
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 42 KiB |
|
@ -112,6 +112,7 @@ export default {
|
|||
CREATE_DISCUSSION_CREATE_F: 'create_discussion_create_f',
|
||||
CREATE_DISCUSSION_SELECT_CHANNEL: 'create_discussion_select_channel',
|
||||
CREATE_DISCUSSION_SELECT_USERS: 'create_discussion_select_users',
|
||||
CREATE_DISCUSSION_TOGGLE_ENCRY: 'create_discussion_toggle_encry',
|
||||
|
||||
// PROFILE VIEW
|
||||
PROFILE_PICK_AVATAR: 'profile_pick_avatar',
|
||||
|
|
|
@ -15,7 +15,7 @@ class Touch extends React.Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
children, onPress, theme, ...props
|
||||
children, onPress, theme, underlayColor, ...props
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -23,7 +23,7 @@ class Touch extends React.Component {
|
|||
ref={this.getRef}
|
||||
onPress={onPress}
|
||||
activeOpacity={1}
|
||||
underlayColor={themes[theme].bannerBackground}
|
||||
underlayColor={underlayColor || themes[theme].bannerBackground}
|
||||
rippleColor={themes[theme].bannerBackground}
|
||||
{...props}
|
||||
>
|
||||
|
@ -36,7 +36,8 @@ class Touch extends React.Component {
|
|||
Touch.propTypes = {
|
||||
children: PropTypes.node,
|
||||
onPress: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
theme: PropTypes.string,
|
||||
underlayColor: PropTypes.string
|
||||
};
|
||||
|
||||
export default Touch;
|
||||
|
|
|
@ -85,7 +85,7 @@ class CreateChannelView extends React.Component {
|
|||
error: PropTypes.object,
|
||||
failure: PropTypes.bool,
|
||||
isFetching: PropTypes.bool,
|
||||
e2eEnabled: PropTypes.bool,
|
||||
encryptionEnabled: PropTypes.bool,
|
||||
users: PropTypes.array.isRequired,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
|
@ -107,7 +107,7 @@ class CreateChannelView extends React.Component {
|
|||
channelName, type, readOnly, broadcast, encrypted
|
||||
} = this.state;
|
||||
const {
|
||||
users, isFetching, e2eEnabled, theme
|
||||
users, isFetching, encryptionEnabled, theme
|
||||
} = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
|
@ -130,7 +130,7 @@ class CreateChannelView extends React.Component {
|
|||
if (nextProps.isFetching !== isFetching) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.e2eEnabled !== e2eEnabled) {
|
||||
if (nextProps.encryptionEnabled !== encryptionEnabled) {
|
||||
return true;
|
||||
}
|
||||
if (!equal(nextProps.users, users)) {
|
||||
|
@ -230,9 +230,9 @@ class CreateChannelView extends React.Component {
|
|||
|
||||
renderEncrypted() {
|
||||
const { type, encrypted } = this.state;
|
||||
const { e2eEnabled } = this.props;
|
||||
const { encryptionEnabled } = this.props;
|
||||
|
||||
if (!e2eEnabled) {
|
||||
if (!encryptionEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -366,7 +366,7 @@ class CreateChannelView extends React.Component {
|
|||
const mapStateToProps = state => ({
|
||||
baseUrl: state.server.server,
|
||||
isFetching: state.createChannel.isFetching,
|
||||
e2eEnabled: state.settings.E2E_Enable,
|
||||
encryptionEnabled: state.encryption.enabled,
|
||||
users: state.selectedUsers.users,
|
||||
user: getUserSelector(state)
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ const SelectChannel = ({
|
|||
|
||||
const getChannels = debounce(async(keyword = '') => {
|
||||
try {
|
||||
const res = await RocketChat.search({ text: keyword, filterUsers: false });
|
||||
const res = await RocketChat.localSearch({ text: keyword });
|
||||
setChannels(res);
|
||||
} catch {
|
||||
// do nothing
|
||||
|
@ -47,7 +47,7 @@ const SelectChannel = ({
|
|||
value={initial && [initial]}
|
||||
disabled={initial}
|
||||
options={channels.map(channel => ({
|
||||
value: channel.rid,
|
||||
value: channel,
|
||||
text: { text: RocketChat.getRoomTitle(channel) },
|
||||
imageUrl: getAvatar(channel)
|
||||
}))}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ScrollView, Text } from 'react-native';
|
||||
import { ScrollView, Text, Switch } from 'react-native';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
import Loading from '../../containers/Loading';
|
||||
|
@ -10,7 +10,7 @@ import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
|||
import I18n from '../../i18n';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import TextInput from '../../containers/TextInput';
|
||||
|
@ -26,6 +26,7 @@ import styles from './styles';
|
|||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import { goRoom } from '../../utils/goRoom';
|
||||
import { logEvent, events } from '../../utils/log';
|
||||
import { E2E_ROOM_TYPES } from '../../lib/encryption/constants';
|
||||
|
||||
class CreateChannelView extends React.Component {
|
||||
propTypes = {
|
||||
|
@ -41,7 +42,8 @@ class CreateChannelView extends React.Component {
|
|||
theme: PropTypes.string,
|
||||
isMasterDetail: PropTypes.bool,
|
||||
blockUnauthenticatedAccess: PropTypes.bool,
|
||||
serverVersion: PropTypes.string
|
||||
serverVersion: PropTypes.string,
|
||||
encryptionEnabled: PropTypes.bool
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -54,7 +56,8 @@ class CreateChannelView extends React.Component {
|
|||
message,
|
||||
name: message?.msg || '',
|
||||
users: [],
|
||||
reply: ''
|
||||
reply: '',
|
||||
encrypted: props.encryptionEnabled
|
||||
};
|
||||
this.setHeader();
|
||||
}
|
||||
|
@ -109,13 +112,13 @@ class CreateChannelView extends React.Component {
|
|||
|
||||
submit = () => {
|
||||
const {
|
||||
name: t_name, channel: { prid, rid }, message: { id: pmid }, reply, users
|
||||
name: t_name, channel: { prid, rid }, message: { id: pmid }, reply, users, encrypted
|
||||
} = this.state;
|
||||
const { create } = this.props;
|
||||
|
||||
// create discussion
|
||||
create({
|
||||
prid: prid || rid, pmid, t_name, reply, users
|
||||
prid: prid || rid, pmid, t_name, reply, users, encrypted
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -134,7 +137,7 @@ class CreateChannelView extends React.Component {
|
|||
|
||||
selectChannel = ({ value }) => {
|
||||
logEvent(events.CREATE_DISCUSSION_SELECT_CHANNEL);
|
||||
this.setState({ channel: { rid: value } });
|
||||
this.setState({ channel: value, encrypted: value?.encrypted });
|
||||
}
|
||||
|
||||
selectUsers = ({ value }) => {
|
||||
|
@ -142,10 +145,17 @@ class CreateChannelView extends React.Component {
|
|||
this.setState({ users: value });
|
||||
}
|
||||
|
||||
onEncryptedChange = (value) => {
|
||||
logEvent(events.CREATE_DISCUSSION_TOGGLE_ENCRY);
|
||||
this.setState({ encrypted: value });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name, users } = this.state;
|
||||
const {
|
||||
server, user, loading, blockUnauthenticatedAccess, theme, serverVersion
|
||||
name, users, encrypted, channel
|
||||
} = this.state;
|
||||
const {
|
||||
server, user, loading, blockUnauthenticatedAccess, theme, serverVersion, encryptionEnabled
|
||||
} = this.props;
|
||||
return (
|
||||
<KeyboardView
|
||||
|
@ -185,15 +195,17 @@ class CreateChannelView extends React.Component {
|
|||
serverVersion={serverVersion}
|
||||
theme={theme}
|
||||
/>
|
||||
<TextInput
|
||||
multiline
|
||||
textAlignVertical='top'
|
||||
label={I18n.t('Your_message')}
|
||||
inputStyle={styles.multiline}
|
||||
theme={theme}
|
||||
placeholder={I18n.t('Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture')}
|
||||
onChangeText={text => this.setState({ reply: text })}
|
||||
/>
|
||||
{encryptionEnabled && E2E_ROOM_TYPES[channel?.t]
|
||||
? (
|
||||
<>
|
||||
<Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Encrypted')}</Text>
|
||||
<Switch
|
||||
value={encrypted}
|
||||
onValueChange={this.onEncryptedChange}
|
||||
trackColor={SWITCH_TRACK_COLOR}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
<Loading visible={loading} />
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
|
@ -211,7 +223,8 @@ const mapStateToProps = state => ({
|
|||
result: state.createDiscussion.result,
|
||||
blockUnauthenticatedAccess: state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? true,
|
||||
serverVersion: state.share.server.version || state.server.version,
|
||||
isMasterDetail: state.app.isMasterDetail
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
encryptionEnabled: state.encryption.enabled
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
@ -80,12 +80,18 @@ class E2EEncryptionSecurityView extends React.Component {
|
|||
title: I18n.t('Are_you_sure_question_mark'),
|
||||
message: I18n.t('E2E_encryption_reset_message'),
|
||||
confirmationText: I18n.t('E2E_encryption_reset_confirmation'),
|
||||
onPress: () => {
|
||||
onPress: async() => {
|
||||
logEvent(events.E2E_SEC_RESET_OWN_KEY);
|
||||
try {
|
||||
RocketChat.e2eResetOwnKey();
|
||||
const { logout } = this.props;
|
||||
logout();
|
||||
const res = await RocketChat.e2eResetOwnKey();
|
||||
/**
|
||||
* It might return an empty object when TOTP is enabled,
|
||||
* that's why we're using strict equality to boolean
|
||||
*/
|
||||
if (res === true) {
|
||||
const { logout } = this.props;
|
||||
logout();
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
showErrorAlert(I18n.t('E2E_encryption_reset_error'));
|
||||
|
@ -96,9 +102,8 @@ class E2EEncryptionSecurityView extends React.Component {
|
|||
|
||||
renderChangePassword = () => {
|
||||
const { newPassword } = this.state;
|
||||
const { theme } = this.props;
|
||||
const { hasPrivateKey } = Encryption;
|
||||
if (!hasPrivateKey) {
|
||||
const { theme, encryptionEnabled } = this.props;
|
||||
if (!encryptionEnabled) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
|
@ -161,7 +166,8 @@ class E2EEncryptionSecurityView extends React.Component {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
server: state.server.server,
|
||||
user: getUserSelector(state)
|
||||
user: getUserSelector(state),
|
||||
encryptionEnabled: state.encryption.enabled
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
@ -179,6 +185,7 @@ E2EEncryptionSecurityView.propTypes = {
|
|||
id: PropTypes.string
|
||||
}),
|
||||
server: PropTypes.string,
|
||||
encryptionEnabled: PropTypes.bool,
|
||||
logout: PropTypes.func
|
||||
};
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ const Item = ({
|
|||
}) => (
|
||||
<Touch style={styles.container} onPress={() => onPress(item.url)} theme={theme} testID={`server-history-${ item.url }`}>
|
||||
<View style={styles.content}>
|
||||
<Text style={[styles.server, { color: themes[theme].bodyText }]}>{item.url}</Text>
|
||||
<Text style={[styles.username, { color: themes[theme].auxiliaryText }]}>{item.username}</Text>
|
||||
<Text numberOfLines={1} style={[styles.server, { color: themes[theme].bodyText }]}>{item.url}</Text>
|
||||
<Text numberOfLines={1} style={[styles.username, { color: themes[theme].auxiliaryText }]}>{item.username}</Text>
|
||||
</View>
|
||||
<BorderlessButton onPress={() => onDelete(item)} testID={`server-history-delete-${ item.url }`}>
|
||||
<CustomIcon name='delete' size={24} color={themes[theme].auxiliaryText} />
|
||||
|
|
|
@ -6,6 +6,7 @@ import TextInput from '../../../containers/TextInput';
|
|||
import * as List from '../../../containers/List';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import Item from './Item';
|
||||
import I18n from '../../../i18n';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -42,8 +43,8 @@ const ServerInput = ({
|
|||
return (
|
||||
<View style={styles.container}>
|
||||
<TextInput
|
||||
label='Enter workspace URL'
|
||||
placeholder='Ex. your-company.rocket.chat'
|
||||
label={I18n.t('Enter_workspace_URL')}
|
||||
placeholder={I18n.t('Workspace_URL_Example')}
|
||||
containerStyle={styles.inputContainer}
|
||||
value={text}
|
||||
returnKeyType='send'
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
View, Text, Image, BackHandler, Linking
|
||||
View, Text, Image, Linking
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import Orientation from 'react-native-orientation-locker';
|
||||
|
||||
import { appStart as appStartAction, ROOT_BACKGROUND } from '../../actions/app';
|
||||
import I18n from '../../i18n';
|
||||
import Button from '../../containers/Button';
|
||||
import styles from './styles';
|
||||
|
@ -23,7 +21,6 @@ class OnboardingView extends React.Component {
|
|||
|
||||
static propTypes = {
|
||||
navigation: PropTypes.object,
|
||||
appStart: PropTypes.func,
|
||||
theme: PropTypes.string
|
||||
}
|
||||
|
||||
|
@ -34,18 +31,6 @@ class OnboardingView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { navigation } = this.props;
|
||||
this.unsubscribeFocus = navigation.addListener('focus', () => {
|
||||
this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||
});
|
||||
this.unsubscribeBlur = navigation.addListener('blur', () => {
|
||||
if (this.backHandler && this.backHandler.remove) {
|
||||
this.backHandler.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
const { theme } = this.props;
|
||||
if (theme !== nextProps.theme) {
|
||||
|
@ -54,21 +39,6 @@ class OnboardingView extends React.Component {
|
|||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.unsubscribeFocus) {
|
||||
this.unsubscribeFocus();
|
||||
}
|
||||
if (this.unsubscribeBlur) {
|
||||
this.unsubscribeBlur();
|
||||
}
|
||||
}
|
||||
|
||||
handleBackPress = () => {
|
||||
const { appStart } = this.props;
|
||||
appStart({ root: ROOT_BACKGROUND });
|
||||
return false;
|
||||
}
|
||||
|
||||
connectServer = () => {
|
||||
logEvent(events.ONBOARD_JOIN_A_WORKSPACE);
|
||||
const { navigation } = this.props;
|
||||
|
@ -89,7 +59,7 @@ class OnboardingView extends React.Component {
|
|||
return (
|
||||
<FormContainer theme={theme} testID='onboarding-view'>
|
||||
<FormContainerInner>
|
||||
<Image style={styles.onboarding} source={{ uri: 'logo' }} fadeDuration={0} />
|
||||
<Image style={styles.onboarding} source={require('../../static/images/logo.png')} fadeDuration={0} />
|
||||
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Onboarding_title')}</Text>
|
||||
<Text style={[styles.subtitle, { color: themes[theme].controlText }]}>{I18n.t('Onboarding_subtitle')}</Text>
|
||||
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Onboarding_description')}</Text>
|
||||
|
@ -116,8 +86,4 @@ class OnboardingView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
appStart: params => dispatch(appStartAction(params))
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(withTheme(OnboardingView));
|
||||
export default withTheme(OnboardingView);
|
||||
|
|
|
@ -11,8 +11,8 @@ export default StyleSheet.create({
|
|||
marginBottom: verticalScale(50),
|
||||
maxHeight: verticalScale(150),
|
||||
resizeMode: 'contain',
|
||||
width: 80,
|
||||
height: 70
|
||||
width: 100,
|
||||
height: 100
|
||||
},
|
||||
title: {
|
||||
...sharedStyles.textBold,
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
} from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import _ from 'lodash';
|
||||
import semver from 'semver';
|
||||
|
||||
import Touch from '../../utils/touch';
|
||||
import { setLoading as setLoadingAction } from '../../actions/selectedUsers';
|
||||
|
@ -46,11 +47,12 @@ class RoomActionsView extends React.Component {
|
|||
route: PropTypes.object,
|
||||
leaveRoom: PropTypes.func,
|
||||
jitsiEnabled: PropTypes.bool,
|
||||
e2eEnabled: PropTypes.bool,
|
||||
encryptionEnabled: PropTypes.bool,
|
||||
setLoadingInvite: PropTypes.func,
|
||||
closeRoom: PropTypes.func,
|
||||
theme: PropTypes.string,
|
||||
fontScale: PropTypes.number
|
||||
fontScale: PropTypes.number,
|
||||
serverVersion: PropTypes.string
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -71,7 +73,8 @@ class RoomActionsView extends React.Component {
|
|||
canInviteUser: false,
|
||||
canForwardGuest: false,
|
||||
canReturnQueue: false,
|
||||
canEdit: false
|
||||
canEdit: false,
|
||||
canToggleEncryption: false
|
||||
};
|
||||
if (room && room.observe && room.rid) {
|
||||
this.roomObservable = room.observe();
|
||||
|
@ -120,6 +123,7 @@ class RoomActionsView extends React.Component {
|
|||
this.canAddUser();
|
||||
this.canInviteUser();
|
||||
this.canEdit();
|
||||
this.canToggleEncryption();
|
||||
|
||||
// livechat permissions
|
||||
if (room.t === 'l') {
|
||||
|
@ -152,7 +156,6 @@ class RoomActionsView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/sort-comp
|
||||
canAddUser = async() => {
|
||||
const { room, joined } = this.state;
|
||||
const { rid, t } = room;
|
||||
|
@ -193,6 +196,15 @@ class RoomActionsView extends React.Component {
|
|||
this.setState({ canEdit });
|
||||
}
|
||||
|
||||
canToggleEncryption = async() => {
|
||||
const { room } = this.state;
|
||||
const { rid } = room;
|
||||
const permissions = await RocketChat.hasPermission(['toggle-room-e2e-encryption'], rid);
|
||||
|
||||
const canToggleEncryption = permissions && permissions['toggle-room-e2e-encryption'];
|
||||
this.setState({ canToggleEncryption });
|
||||
}
|
||||
|
||||
canViewMembers = async() => {
|
||||
const { room } = this.state;
|
||||
const { rid, t, broadcast } = room;
|
||||
|
@ -235,13 +247,21 @@ class RoomActionsView extends React.Component {
|
|||
}
|
||||
|
||||
renderEncryptedSwitch = () => {
|
||||
const { room } = this.state;
|
||||
const { room, canToggleEncryption, canEdit } = this.state;
|
||||
const { encrypted } = room;
|
||||
const { serverVersion } = this.props;
|
||||
let hasPermission = false;
|
||||
if (serverVersion && semver.lt(semver.coerce(serverVersion), '3.11.0')) {
|
||||
hasPermission = canEdit;
|
||||
} else {
|
||||
hasPermission = canToggleEncryption;
|
||||
}
|
||||
return (
|
||||
<Switch
|
||||
value={encrypted}
|
||||
trackColor={SWITCH_TRACK_COLOR}
|
||||
onValueChange={this.toggleEncrypted}
|
||||
disabled={!hasPermission}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -480,15 +500,12 @@ class RoomActionsView extends React.Component {
|
|||
}
|
||||
|
||||
renderE2EEncryption = () => {
|
||||
const {
|
||||
room, canEdit
|
||||
} = this.state;
|
||||
const { e2eEnabled } = this.props;
|
||||
const { room } = this.state;
|
||||
const { encryptionEnabled } = this.props;
|
||||
|
||||
// If can edit this room
|
||||
// If this room type can be Encrypted
|
||||
// If e2e is enabled for this server
|
||||
if (canEdit && E2E_ROOM_TYPES[room?.t] && e2eEnabled) {
|
||||
// If this room type can be encrypted
|
||||
// If e2e is enabled
|
||||
if (E2E_ROOM_TYPES[room?.t] && encryptionEnabled) {
|
||||
return (
|
||||
<List.Section>
|
||||
<List.Separator />
|
||||
|
@ -847,7 +864,8 @@ class RoomActionsView extends React.Component {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
jitsiEnabled: state.settings.Jitsi_Enabled || false,
|
||||
e2eEnabled: state.settings.E2E_Enable || false
|
||||
encryptionEnabled: state.encryption.enabled,
|
||||
serverVersion: state.server.version
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
@ -60,7 +60,7 @@ class RoomInfoEditView extends React.Component {
|
|||
route: PropTypes.object,
|
||||
deleteRoom: PropTypes.func,
|
||||
serverVersion: PropTypes.string,
|
||||
e2eEnabled: PropTypes.bool,
|
||||
encryptionEnabled: PropTypes.bool,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
|
@ -414,7 +414,7 @@ class RoomInfoEditView extends React.Component {
|
|||
const {
|
||||
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived, enableSysMes, encrypted, avatar
|
||||
} = this.state;
|
||||
const { serverVersion, e2eEnabled, theme } = this.props;
|
||||
const { serverVersion, encryptionEnabled, theme } = this.props;
|
||||
const { dangerColor } = themes[theme];
|
||||
|
||||
return (
|
||||
|
@ -561,7 +561,7 @@ class RoomInfoEditView extends React.Component {
|
|||
{this.renderSystemMessages()}
|
||||
</SwitchContainer>
|
||||
) : null}
|
||||
{e2eEnabled ? (
|
||||
{encryptionEnabled ? (
|
||||
<SwitchContainer
|
||||
value={encrypted}
|
||||
disabled={!t}
|
||||
|
@ -665,7 +665,7 @@ class RoomInfoEditView extends React.Component {
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
serverVersion: state.share.server.version || state.server.version,
|
||||
e2eEnabled: state.settings.E2E_Enable || false
|
||||
encryptionEnabled: state.encryption.enabled
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||
|
||||
import { withTheme } from '../../../theme';
|
||||
import { CustomIcon } from '../../../lib/Icons';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import I18n from '../../../i18n';
|
||||
import styles from '../styles';
|
||||
import { E2E_BANNER_TYPE } from '../../../lib/encryption/constants';
|
||||
|
||||
const Encryption = React.memo(({
|
||||
searching,
|
||||
goEncryption,
|
||||
encryptionBanner,
|
||||
theme
|
||||
}) => {
|
||||
if (searching > 0 || !encryptionBanner) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let text = I18n.t('Save_Your_Encryption_Password');
|
||||
if (encryptionBanner === E2E_BANNER_TYPE.REQUEST_PASSWORD) {
|
||||
text = I18n.t('Enter_Your_E2E_Password');
|
||||
}
|
||||
|
||||
return (
|
||||
<BorderlessButton
|
||||
style={[styles.encryptionButton, { backgroundColor: themes[theme].actionTintColor }]}
|
||||
theme={theme}
|
||||
onPress={goEncryption}
|
||||
testID='listheader-encryption'
|
||||
accessibilityLabel={text}
|
||||
>
|
||||
<CustomIcon name='encrypted' size={24} color={themes[theme].buttonText} style={styles.encryptionIcon} />
|
||||
<Text style={[styles.encryptionText, { color: themes[theme].buttonText }]}>{text}</Text>
|
||||
</BorderlessButton>
|
||||
);
|
||||
});
|
||||
|
||||
Encryption.propTypes = {
|
||||
searching: PropTypes.bool,
|
||||
goEncryption: PropTypes.func,
|
||||
encryptionBanner: PropTypes.string,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default withTheme(Encryption);
|
|
@ -1,45 +0,0 @@
|
|||
import React from 'react';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Touch from '../../../utils/touch';
|
||||
import { CustomIcon } from '../../../lib/Icons';
|
||||
import I18n from '../../../i18n';
|
||||
import styles from '../styles';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { withTheme } from '../../../theme';
|
||||
|
||||
|
||||
const Sort = React.memo(({
|
||||
searching, sortBy, toggleSort, theme
|
||||
}) => {
|
||||
if (searching > 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Touch
|
||||
onPress={toggleSort}
|
||||
theme={theme}
|
||||
style={{ backgroundColor: themes[theme].headerSecondaryBackground }}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.dropdownContainerHeader,
|
||||
{ borderBottomWidth: StyleSheet.hairlineWidth, borderColor: themes[theme].separatorColor }
|
||||
]}
|
||||
>
|
||||
<CustomIcon style={[styles.sortIcon, { color: themes[theme].auxiliaryText }]} size={22} name='sort' />
|
||||
<Text style={[styles.sortToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
||||
</View>
|
||||
</Touch>
|
||||
);
|
||||
});
|
||||
|
||||
Sort.propTypes = {
|
||||
searching: PropTypes.bool,
|
||||
sortBy: PropTypes.string,
|
||||
theme: PropTypes.string,
|
||||
toggleSort: PropTypes.func
|
||||
};
|
||||
|
||||
export default withTheme(Sort);
|
|
@ -1,8 +1,11 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Sort from './Sort';
|
||||
import Encryption from './Encryption';
|
||||
import { withTheme } from '../../../theme';
|
||||
import I18n from '../../../i18n';
|
||||
import * as List from '../../../containers/List';
|
||||
import { E2E_BANNER_TYPE } from '../../../lib/encryption/constants';
|
||||
import { themes } from '../../../constants/colors';
|
||||
|
||||
import OmnichannelStatus from '../../../ee/omnichannel/containers/OmnichannelStatus';
|
||||
|
||||
|
@ -15,14 +18,55 @@ const ListHeader = React.memo(({
|
|||
queueSize,
|
||||
inquiryEnabled,
|
||||
encryptionBanner,
|
||||
user
|
||||
}) => (
|
||||
<>
|
||||
<Encryption searching={searching} goEncryption={goEncryption} encryptionBanner={encryptionBanner} />
|
||||
<Sort searching={searching} sortBy={sortBy} toggleSort={toggleSort} />
|
||||
<OmnichannelStatus searching={searching} goQueue={goQueue} inquiryEnabled={inquiryEnabled} queueSize={queueSize} user={user} />
|
||||
</>
|
||||
));
|
||||
user,
|
||||
theme
|
||||
}) => {
|
||||
const sortTitle = I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') });
|
||||
|
||||
if (searching) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{encryptionBanner
|
||||
? (
|
||||
<>
|
||||
<List.Item
|
||||
title={
|
||||
encryptionBanner === E2E_BANNER_TYPE.REQUEST_PASSWORD
|
||||
? 'Enter_Your_E2E_Password'
|
||||
: 'Save_Your_Encryption_Password'
|
||||
}
|
||||
left={() => <List.Icon name='encrypted' color={themes[theme].buttonText} />}
|
||||
underlayColor={themes[theme].tintActive}
|
||||
backgroundColor={themes[theme].actionTintColor}
|
||||
color={themes[theme].buttonText}
|
||||
onPress={goEncryption}
|
||||
testID='listheader-encryption'
|
||||
/>
|
||||
<List.Separator />
|
||||
</>
|
||||
)
|
||||
: null}
|
||||
<List.Item
|
||||
title={sortTitle}
|
||||
left={() => <List.Icon name='sort' />}
|
||||
color={themes[theme].auxiliaryText}
|
||||
onPress={toggleSort}
|
||||
translateTitle={false}
|
||||
/>
|
||||
<List.Separator />
|
||||
<OmnichannelStatus
|
||||
searching={searching}
|
||||
goQueue={goQueue}
|
||||
inquiryEnabled={inquiryEnabled}
|
||||
queueSize={queueSize}
|
||||
user={user}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
ListHeader.propTypes = {
|
||||
searching: PropTypes.bool,
|
||||
|
@ -33,7 +77,8 @@ ListHeader.propTypes = {
|
|||
queueSize: PropTypes.number,
|
||||
inquiryEnabled: PropTypes.bool,
|
||||
encryptionBanner: PropTypes.string,
|
||||
user: PropTypes.object
|
||||
user: PropTypes.object,
|
||||
theme: PropTypes.string
|
||||
};
|
||||
|
||||
export default ListHeader;
|
||||
export default withTheme(ListHeader);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import {
|
||||
View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Image, Pressable
|
||||
View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect, batch } from 'react-redux';
|
||||
|
@ -14,12 +14,12 @@ import styles from './styles';
|
|||
import RocketChat from '../../lib/rocketchat';
|
||||
import I18n from '../../i18n';
|
||||
import EventEmitter from '../../utils/events';
|
||||
import Check from '../../containers/Check';
|
||||
import ServerItem from '../../presentation/ServerItem';
|
||||
import database from '../../lib/database';
|
||||
import { themes } from '../../constants/colors';
|
||||
import { withTheme } from '../../theme';
|
||||
import { KEY_COMMAND, handleCommandSelectServer } from '../../commands';
|
||||
import { isTablet, isIOS } from '../../utils/deviceInfo';
|
||||
import { isTablet } from '../../utils/deviceInfo';
|
||||
import { localAuthenticate } from '../../utils/localAuthentication';
|
||||
import { showConfirmationAlert } from '../../utils/info';
|
||||
import { logEvent, events } from '../../utils/log';
|
||||
|
@ -143,7 +143,7 @@ class ServerDropdown extends Component {
|
|||
}, ANIMATION_DURATION);
|
||||
}
|
||||
|
||||
select = async(server) => {
|
||||
select = async(server, version) => {
|
||||
const {
|
||||
server: currentServer, selectServerRequest, isMasterDetail
|
||||
} = this.props;
|
||||
|
@ -163,7 +163,7 @@ class ServerDropdown extends Component {
|
|||
}, ANIMATION_DURATION);
|
||||
} else {
|
||||
await localAuthenticate(server);
|
||||
selectServerRequest(server);
|
||||
selectServerRequest(server, version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,43 +202,13 @@ class ServerDropdown extends Component {
|
|||
const { server, theme } = this.props;
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => this.select(item.id)}
|
||||
<ServerItem
|
||||
item={item}
|
||||
onPress={() => this.select(item.id, item.version)}
|
||||
onLongPress={() => (item.id === server || this.remove(item.id))}
|
||||
testID={`rooms-list-header-server-${ item.id }`}
|
||||
android_ripple={{
|
||||
color: themes[theme].bannerBackground
|
||||
}}
|
||||
style={({ pressed }) => ({
|
||||
backgroundColor: isIOS && pressed
|
||||
? themes[theme].bannerBackground
|
||||
: 'transparent'
|
||||
})}
|
||||
>
|
||||
<View style={styles.serverItemContainer}>
|
||||
{item.iconURL
|
||||
? (
|
||||
<Image
|
||||
source={{ uri: item.iconURL }}
|
||||
defaultSource={{ uri: 'logo' }}
|
||||
style={styles.serverIcon}
|
||||
onError={() => console.warn('error loading serverIcon')}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Image
|
||||
source={{ uri: 'logo' }}
|
||||
style={styles.serverIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<View style={styles.serverTextContainer}>
|
||||
<Text style={[styles.serverName, { color: themes[theme].titleText }]} numberOfLines={1}>{item.name || item.id}</Text>
|
||||
<Text style={[styles.serverUrl, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{item.id}</Text>
|
||||
</View>
|
||||
{item.id === server ? <Check theme={theme} /> : null}
|
||||
</View>
|
||||
</Pressable>
|
||||
hasCheck={item.id === server}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -313,7 +283,7 @@ const mapStateToProps = state => ({
|
|||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleServerDropdown: () => dispatch(toggleServerDropdownAction()),
|
||||
selectServerRequest: server => dispatch(selectServerRequestAction(server)),
|
||||
selectServerRequest: (server, version) => dispatch(selectServerRequestAction(server, version, true, true)),
|
||||
appStart: params => dispatch(appStartAction(params)),
|
||||
initAdd: previousServer => dispatch(serverInitAddAction(previousServer))
|
||||
});
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
import React from 'react';
|
||||
import { View, Text, Image } from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from '../styles';
|
||||
import Touch from '../../../utils/touch';
|
||||
import I18n from '../../../i18n';
|
||||
import { CustomIcon } from '../../../lib/Icons';
|
||||
import Check from '../../../containers/Check';
|
||||
import { themes } from '../../../constants/colors';
|
||||
|
||||
|
||||
export const SortItemButton = ({ children, onPress, theme }) => (
|
||||
<Touch
|
||||
style={styles.sortItemButton}
|
||||
onPress={onPress}
|
||||
theme={theme}
|
||||
>
|
||||
{children}
|
||||
</Touch>
|
||||
);
|
||||
|
||||
SortItemButton.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
onPress: PropTypes.func
|
||||
};
|
||||
|
||||
export const SortItemContent = ({
|
||||
label, icon, imageUri, checked, theme
|
||||
}) => (
|
||||
<View style={styles.sortItemContainer}>
|
||||
{icon && <CustomIcon style={[styles.sortIcon, { color: themes[theme].controlText }]} size={22} name={icon} />}
|
||||
{imageUri && <Image style={[styles.sortIcon, { tintColor: themes[theme].controlText }]} source={{ uri: imageUri }} />}
|
||||
<Text style={[styles.sortItemText, { color: themes[theme].controlText }]}>{I18n.t(label)}</Text>
|
||||
{checked ? <Check theme={theme} /> : null}
|
||||
</View>
|
||||
);
|
||||
|
||||
SortItemContent.propTypes = {
|
||||
theme: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
imageUri: PropTypes.string,
|
||||
checked: PropTypes.bool
|
||||
};
|
|
@ -1,21 +1,19 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
View, Text, Animated, Easing, TouchableWithoutFeedback
|
||||
Animated, Easing, TouchableWithoutFeedback
|
||||
} from 'react-native';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
import styles from '../styles';
|
||||
import Touch from '../../../utils/touch';
|
||||
import * as List from '../../../containers/List';
|
||||
import RocketChat from '../../../lib/rocketchat';
|
||||
import { setPreference } from '../../../actions/sortPreferences';
|
||||
import log, { logEvent, events } from '../../../utils/log';
|
||||
import I18n from '../../../i18n';
|
||||
import { CustomIcon } from '../../../lib/Icons';
|
||||
import { withTheme } from '../../../theme';
|
||||
import { themes } from '../../../constants/colors';
|
||||
import { SortItemButton, SortItemContent } from './Item';
|
||||
import { headerHeight } from '../../../containers/Header';
|
||||
|
||||
const ANIMATION_DURATION = 200;
|
||||
|
@ -113,6 +111,11 @@ class Sort extends PureComponent {
|
|||
).start(() => close());
|
||||
}
|
||||
|
||||
renderCheck = () => {
|
||||
const { theme } = this.props;
|
||||
return <List.Icon name='check' color={themes[theme].tintColor} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isMasterDetail, insets } = this.props;
|
||||
const statusBarHeight = insets?.top ?? 0;
|
||||
|
@ -150,58 +153,50 @@ class Sort extends PureComponent {
|
|||
}
|
||||
]}
|
||||
>
|
||||
<Touch
|
||||
<List.Item
|
||||
title={I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}
|
||||
left={() => <List.Icon name='sort' />}
|
||||
color={themes[theme].auxiliaryText}
|
||||
onPress={this.close}
|
||||
theme={theme}
|
||||
>
|
||||
<View style={[styles.dropdownContainerHeader, { borderColor: themes[theme].separatorColor }]}>
|
||||
<View style={styles.sortItemContainer}>
|
||||
<CustomIcon style={[styles.sortIcon, { color: themes[theme].auxiliaryText }]} size={22} name='sort' />
|
||||
<Text style={[styles.sortToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Touch>
|
||||
<SortItemButton onPress={this.sortByName} theme={theme}>
|
||||
<SortItemContent
|
||||
icon='sort-az'
|
||||
label='Alphabetical'
|
||||
checked={sortBy === 'alphabetical'}
|
||||
theme={theme}
|
||||
/>
|
||||
</SortItemButton>
|
||||
<SortItemButton onPress={this.sortByActivity} theme={theme}>
|
||||
<SortItemContent
|
||||
icon='clock'
|
||||
label='Activity'
|
||||
checked={sortBy === 'activity'}
|
||||
theme={theme}
|
||||
/>
|
||||
</SortItemButton>
|
||||
<View style={[styles.sortSeparator, { backgroundColor: themes[theme].separatorColor }]} />
|
||||
<SortItemButton onPress={this.toggleGroupByType} theme={theme}>
|
||||
<SortItemContent
|
||||
icon='group-by-type'
|
||||
label='Group_by_type'
|
||||
checked={groupByType}
|
||||
theme={theme}
|
||||
/>
|
||||
</SortItemButton>
|
||||
<SortItemButton onPress={this.toggleGroupByFavorites} theme={theme}>
|
||||
<SortItemContent
|
||||
icon='star'
|
||||
label='Group_by_favorites'
|
||||
checked={showFavorites}
|
||||
theme={theme}
|
||||
/>
|
||||
</SortItemButton>
|
||||
<SortItemButton onPress={this.toggleUnread} theme={theme}>
|
||||
<SortItemContent
|
||||
icon='unread-on-top-disabled'
|
||||
label='Unread_on_top'
|
||||
checked={showUnread}
|
||||
theme={theme}
|
||||
/>
|
||||
</SortItemButton>
|
||||
translateTitle={false}
|
||||
/>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Alphabetical'
|
||||
left={() => <List.Icon name='sort-az' />}
|
||||
color={themes[theme].auxiliaryText}
|
||||
onPress={this.sortByName}
|
||||
right={() => (sortBy === 'alphabetical' ? this.renderCheck() : null)}
|
||||
/>
|
||||
<List.Item
|
||||
title='Activity'
|
||||
left={() => <List.Icon name='clock' />}
|
||||
color={themes[theme].auxiliaryText}
|
||||
onPress={this.sortByActivity}
|
||||
right={() => (sortBy === 'activity' ? this.renderCheck() : null)}
|
||||
/>
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Group_by_type'
|
||||
left={() => <List.Icon name='group-by-type' />}
|
||||
color={themes[theme].auxiliaryText}
|
||||
onPress={this.toggleGroupByType}
|
||||
right={() => (groupByType ? this.renderCheck() : null)}
|
||||
/>
|
||||
<List.Item
|
||||
title='Group_by_favorites'
|
||||
left={() => <List.Icon name='star' />}
|
||||
color={themes[theme].auxiliaryText}
|
||||
onPress={this.toggleGroupByFavorites}
|
||||
right={() => (showFavorites ? this.renderCheck() : null)}
|
||||
/>
|
||||
<List.Item
|
||||
title='Unread_on_top'
|
||||
left={() => <List.Icon name='unread-on-top-disabled' />}
|
||||
color={themes[theme].auxiliaryText}
|
||||
onPress={this.toggleUnread}
|
||||
right={() => (showUnread ? this.renderCheck() : null)}
|
||||
/>
|
||||
</Animated.View>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -29,7 +29,6 @@ import {
|
|||
roomsRequest as roomsRequestAction,
|
||||
closeServerDropdown as closeServerDropdownAction
|
||||
} from '../../actions/rooms';
|
||||
import { appStart as appStartAction, ROOT_BACKGROUND } from '../../actions/app';
|
||||
import debounce from '../../utils/debounce';
|
||||
import { isIOS, isTablet } from '../../utils/deviceInfo';
|
||||
import RoomsListHeaderView from './Header';
|
||||
|
@ -117,6 +116,7 @@ class RoomsListView extends React.Component {
|
|||
}),
|
||||
server: PropTypes.string,
|
||||
searchText: PropTypes.string,
|
||||
changingServer: PropTypes.bool,
|
||||
loadingServer: PropTypes.bool,
|
||||
showServerDropdown: PropTypes.bool,
|
||||
showSortDropdown: PropTypes.bool,
|
||||
|
@ -150,8 +150,8 @@ class RoomsListView extends React.Component {
|
|||
console.time(`${ this.constructor.name } init`);
|
||||
console.time(`${ this.constructor.name } mount`);
|
||||
|
||||
this.gotSubscriptions = false;
|
||||
this.animated = false;
|
||||
this.mounted = false;
|
||||
this.count = 0;
|
||||
this.state = {
|
||||
searching: false,
|
||||
|
@ -162,24 +162,14 @@ class RoomsListView extends React.Component {
|
|||
item: {}
|
||||
};
|
||||
this.setHeader();
|
||||
this.getSubscriptions();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
navigation, closeServerDropdown, appState
|
||||
navigation, closeServerDropdown
|
||||
} = this.props;
|
||||
|
||||
/**
|
||||
* - When didMount is triggered and appState is foreground,
|
||||
* it means the user is logging in and selectServer has ran, so we can getSubscriptions
|
||||
*
|
||||
* - When didMount is triggered and appState is background,
|
||||
* it means the user has resumed the app, so selectServer needs to be triggered,
|
||||
* which is going to change server and getSubscriptions will be triggered by componentWillReceiveProps
|
||||
*/
|
||||
if (appState === 'foreground') {
|
||||
this.getSubscriptions();
|
||||
}
|
||||
this.mounted = true;
|
||||
|
||||
if (isTablet) {
|
||||
EventEmitter.addEventListener(KEY_COMMAND, this.handleCommands);
|
||||
|
@ -206,17 +196,17 @@ class RoomsListView extends React.Component {
|
|||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
const { loadingServer, searchText, server } = this.props;
|
||||
const {
|
||||
loadingServer, searchText, server, changingServer
|
||||
} = this.props;
|
||||
|
||||
if (nextProps.server && loadingServer !== nextProps.loadingServer) {
|
||||
if (nextProps.loadingServer) {
|
||||
this.setState({ loading: true });
|
||||
} else {
|
||||
this.getSubscriptions();
|
||||
}
|
||||
// when the server is changed
|
||||
if (server !== nextProps.server && loadingServer !== nextProps.loadingServer && nextProps.loadingServer) {
|
||||
this.setState({ loading: true });
|
||||
}
|
||||
if (server && server !== nextProps.server) {
|
||||
this.gotSubscriptions = false;
|
||||
// when the server is changing and stopped loading
|
||||
if (changingServer && loadingServer !== nextProps.loadingServer && !nextProps.loadingServer) {
|
||||
this.getSubscriptions();
|
||||
}
|
||||
if (searchText !== nextProps.searchText) {
|
||||
this.search(nextProps.searchText);
|
||||
|
@ -508,11 +498,17 @@ class RoomsListView extends React.Component {
|
|||
tempChats = chats;
|
||||
}
|
||||
|
||||
this.internalSetState({
|
||||
chats: tempChats,
|
||||
chatsUpdate,
|
||||
loading: false
|
||||
});
|
||||
if (this.mounted) {
|
||||
this.internalSetState({
|
||||
chats: tempChats,
|
||||
chatsUpdate,
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
this.state.chats = tempChats;
|
||||
this.state.chatsUpdate = chatsUpdate;
|
||||
this.state.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -552,12 +548,10 @@ class RoomsListView extends React.Component {
|
|||
|
||||
handleBackPress = () => {
|
||||
const { searching } = this.state;
|
||||
const { appStart } = this.props;
|
||||
if (searching) {
|
||||
this.cancelSearch();
|
||||
return true;
|
||||
}
|
||||
appStart({ root: ROOT_BACKGROUND });
|
||||
return false;
|
||||
};
|
||||
|
||||
|
@ -1023,6 +1017,7 @@ const mapStateToProps = state => ({
|
|||
user: getUserSelector(state),
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
server: state.server.server,
|
||||
changingServer: state.server.changingServer,
|
||||
connected: state.server.connected,
|
||||
searchText: state.rooms.searchText,
|
||||
loadingServer: state.server.loading,
|
||||
|
@ -1046,7 +1041,6 @@ const mapDispatchToProps = dispatch => ({
|
|||
toggleSortDropdown: () => dispatch(toggleSortDropdownAction()),
|
||||
openSearchHeader: () => dispatch(openSearchHeaderAction()),
|
||||
closeSearchHeader: () => dispatch(closeSearchHeaderAction()),
|
||||
appStart: params => dispatch(appStartAction(params)),
|
||||
roomsRequest: params => dispatch(roomsRequestAction(params)),
|
||||
selectServerRequest: server => dispatch(selectServerRequestAction(server)),
|
||||
closeServerDropdown: () => dispatch(closeServerDropdownAction())
|
||||
|
|
|
@ -15,56 +15,22 @@ export default StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
sortToggleContainerClose: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
width: '100%'
|
||||
},
|
||||
sortToggleText: {
|
||||
fontSize: 16,
|
||||
flex: 1,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
queueToggleText: {
|
||||
fontSize: 16,
|
||||
flex: 1,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
dropdownContainer: {
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth
|
||||
},
|
||||
sortItemButton: {
|
||||
height: 57,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
sortItemContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
sortItemText: {
|
||||
fontSize: 18,
|
||||
flex: 1,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
backdrop: {
|
||||
...StyleSheet.absoluteFill
|
||||
},
|
||||
sortSeparator: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
marginHorizontal: 12,
|
||||
flex: 1
|
||||
},
|
||||
sortIcon: {
|
||||
width: 22,
|
||||
height: 22,
|
||||
marginHorizontal: 12
|
||||
},
|
||||
queueIcon: {
|
||||
marginHorizontal: 12
|
||||
},
|
||||
omnichannelRightContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
groupTitleContainer: {
|
||||
paddingHorizontal: 12,
|
||||
paddingTop: 17,
|
||||
|
@ -90,56 +56,5 @@ export default StyleSheet.create({
|
|||
marginRight: 12,
|
||||
paddingVertical: 10,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
serverItem: {
|
||||
height: 68
|
||||
},
|
||||
serverItemContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
height: 68
|
||||
},
|
||||
serverIcon: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
marginHorizontal: 12,
|
||||
marginVertical: 13,
|
||||
borderRadius: 4,
|
||||
resizeMode: 'contain'
|
||||
},
|
||||
serverTextContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
serverName: {
|
||||
fontSize: 18,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
serverUrl: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
serverSeparator: {
|
||||
height: StyleSheet.hairlineWidth,
|
||||
marginLeft: 72
|
||||
},
|
||||
encryptionButton: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 12
|
||||
},
|
||||
encryptionIcon: {
|
||||
...sharedStyles.textMedium
|
||||
},
|
||||
encryptionText: {
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
marginHorizontal: 16,
|
||||
...sharedStyles.textMedium
|
||||
},
|
||||
omnichannelToggle: {
|
||||
marginRight: 12
|
||||
}
|
||||
});
|
||||
|
|
|
@ -65,10 +65,9 @@ class SelectServerView extends React.Component {
|
|||
const { server, theme } = this.props;
|
||||
return (
|
||||
<ServerItem
|
||||
server={server}
|
||||
onPress={() => this.select(item.id)}
|
||||
item={item}
|
||||
hasCheck
|
||||
hasCheck={item.id === server}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -195,7 +195,7 @@ class ShareListView extends React.Component {
|
|||
Q.where('archived', false),
|
||||
Q.where('open', true),
|
||||
Q.experimentalSkip(0),
|
||||
Q.experimentalTake(50),
|
||||
Q.experimentalTake(20),
|
||||
Q.experimentalSortBy('room_updated_at', Q.desc)
|
||||
];
|
||||
if (text) {
|
||||
|
@ -451,11 +451,9 @@ class ShareListView extends React.Component {
|
|||
ListFooterComponent={!searching && this.renderBorderBottom}
|
||||
ListHeaderComponentStyle={!searching ? { ...styles.borderBottom, borderColor: themes[theme].separatorColor } : {}}
|
||||
ListEmptyComponent={searching && searchText ? this.renderEmptyComponent : null}
|
||||
enableEmptySections
|
||||
removeClippedSubviews
|
||||
keyboardShouldPersistTaps='always'
|
||||
initialNumToRender={12}
|
||||
windowSize={20}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
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'
|
||||
},
|
||||
detailText: {
|
||||
fontSize: 10,
|
||||
marginLeft: 2,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
badgeContainer: {
|
||||
marginLeft: 8,
|
||||
justifyContent: 'center'
|
||||
threadDetails: {
|
||||
marginTop: 8
|
||||
},
|
||||
badge: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: 6
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
marginHorizontal: 8,
|
||||
alignSelf: 'center'
|
||||
},
|
||||
messageContainer: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
Either
|
||||
|
||||
* Install Rocket.Chat meteor app by following this [guide](https://rocket.chat/docs/developer-guides/quick-start).
|
||||
* Install Rocket.Chat meteor app by following this [guide](https://docs.rocket.chat/guides/developer/quick-start).
|
||||
|
||||
Or
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ const room = data.channels.detoxpublic.name;
|
|||
|
||||
async function navigateToRoom() {
|
||||
await searchRoom(room);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0)).toBeVisible().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0).tap();
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@ const joinCode = data.channels.detoxpublicprotected.joinCode
|
|||
|
||||
async function navigateToRoom() {
|
||||
await searchRoom(room);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0)).toBeVisible().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0).tap();
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ describe('Rooms list screen', () => {
|
|||
});
|
||||
|
||||
it('should have room item', async() => {
|
||||
await expect(element(by.id('rooms-list-view-item-general')).atIndex(0)).toExist();
|
||||
await expect(element(by.id('rooms-list-view-item-general'))).toExist();
|
||||
});
|
||||
|
||||
// Render - Header
|
||||
|
|
|
@ -67,12 +67,12 @@ describe('Create room screen', () => {
|
|||
|
||||
it('should select/unselect user', async() => {
|
||||
// Spotlight issues
|
||||
await element(by.id('select-users-view-item-rocket.cat')).atIndex(0).tap();
|
||||
await element(by.id('select-users-view-item-rocket.cat')).tap();
|
||||
await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(10000);
|
||||
await element(by.id('selected-user-rocket.cat')).tap();
|
||||
await waitFor(element(by.id('selected-user-rocket.cat'))).toBeNotVisible().withTimeout(10000);
|
||||
// Spotlight issues
|
||||
await element(by.id('select-users-view-item-rocket.cat')).atIndex(0).tap();
|
||||
await element(by.id('select-users-view-item-rocket.cat')).tap();
|
||||
await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(10000);
|
||||
});
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@ async function navigateToRoomActions(type) {
|
|||
room = data.groups.private.name;
|
||||
}
|
||||
await searchRoom(room);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0)).toExist().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0).tap();
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000);
|
||||
await element(by.id('room-view-header-actions')).tap();
|
||||
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
|
||||
|
|
|
@ -14,8 +14,8 @@ async function navigateToRoomInfo(type) {
|
|||
room = privateRoomName;
|
||||
}
|
||||
await searchRoom(room);
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0)).toExist().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0).tap();
|
||||
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000);
|
||||
await element(by.id(`rooms-list-view-item-${ room }`)).tap();
|
||||
await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000);
|
||||
await element(by.id('room-view-header-actions')).tap();
|
||||
await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000);
|
||||
|
|
|
@ -461,7 +461,7 @@ PODS:
|
|||
- React
|
||||
- ReactNativeUiLib (3.0.4):
|
||||
- React
|
||||
- rn-extensions-share (2.4.0):
|
||||
- rn-extensions-share (2.4.1):
|
||||
- React
|
||||
- rn-fetch-blob (0.12.0):
|
||||
- React-Core
|
||||
|
@ -964,7 +964,7 @@ SPEC CHECKSUMS:
|
|||
ReactCommon: 73d79c7039f473b76db6ff7c6b159c478acbbb3b
|
||||
ReactNativeART: 78edc68dd4a1e675338cd0cd113319cf3a65f2ab
|
||||
ReactNativeUiLib: cde7263a7d308b60161cd286f95c9433e79f2f7d
|
||||
rn-extensions-share: 8db79372089567cbc5aefe8444869bbc808578d3
|
||||
rn-extensions-share: 5fd84a80e6594706f0dfa1884f2d6d591b382cf5
|
||||
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
|
||||
RNBootSplash: b3836aa90c5bec690c6cd3c9ab355fcf98d0fe1d
|
||||
RNCAsyncStorage: d059c3ee71738c39834a627476322a5a8cd5bf36
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "rn-extensions-share",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"summary": "Share-Extension using react-native for both ios and android",
|
||||
"license": "MIT",
|
||||
"authors": {
|
||||
|
|
|
@ -461,7 +461,7 @@ PODS:
|
|||
- React
|
||||
- ReactNativeUiLib (3.0.4):
|
||||
- React
|
||||
- rn-extensions-share (2.4.0):
|
||||
- rn-extensions-share (2.4.1):
|
||||
- React
|
||||
- rn-fetch-blob (0.12.0):
|
||||
- React-Core
|
||||
|
@ -964,7 +964,7 @@ SPEC CHECKSUMS:
|
|||
ReactCommon: 73d79c7039f473b76db6ff7c6b159c478acbbb3b
|
||||
ReactNativeART: 78edc68dd4a1e675338cd0cd113319cf3a65f2ab
|
||||
ReactNativeUiLib: cde7263a7d308b60161cd286f95c9433e79f2f7d
|
||||
rn-extensions-share: 8db79372089567cbc5aefe8444869bbc808578d3
|
||||
rn-extensions-share: 5fd84a80e6594706f0dfa1884f2d6d591b382cf5
|
||||
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
|
||||
RNBootSplash: b3836aa90c5bec690c6cd3c9ab355fcf98d0fe1d
|
||||
RNCAsyncStorage: d059c3ee71738c39834a627476322a5a8cd5bf36
|
||||
|
|
|
@ -1693,7 +1693,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;
|
||||
|
@ -1730,7 +1730,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;
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "icon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 7.8 KiB |
|
@ -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>
|
||||
|
|
10
package.json
|
@ -108,7 +108,7 @@
|
|||
"react-native-scrollable-tab-view": "^1.0.0",
|
||||
"react-native-simple-crypto": "RocketChat/react-native-simple-crypto",
|
||||
"react-native-slowlog": "^1.0.2",
|
||||
"react-native-ui-lib": "RocketChat/react-native-ui-lib",
|
||||
"react-native-ui-lib": "RocketChat/react-native-ui-lib#minor-improvements",
|
||||
"react-native-unimodules": "0.10.1",
|
||||
"react-native-vector-icons": "7.0.0",
|
||||
"react-native-webview": "10.3.2",
|
||||
|
@ -119,7 +119,7 @@
|
|||
"redux-saga": "1.1.3",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"reselect": "4.0.0",
|
||||
"rn-extensions-share": "^2.4.0",
|
||||
"rn-extensions-share": "RocketChat/rn-extensions-share",
|
||||
"rn-fetch-blob": "0.12.0",
|
||||
"rn-root-view": "^1.0.3",
|
||||
"semver": "7.3.2",
|
||||
|
@ -193,7 +193,8 @@
|
|||
"build": "xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
|
||||
"type": "ios.simulator",
|
||||
"device": {
|
||||
"type": "iPhone 11 Pro"
|
||||
"type": "iPhone 11 Pro",
|
||||
"os": "13.7"
|
||||
}
|
||||
},
|
||||
"ios.sim.release": {
|
||||
|
@ -201,7 +202,8 @@
|
|||
"build": "xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Release -sdk iphonesimulator -derivedDataPath ios/build",
|
||||
"type": "ios.simulator",
|
||||
"device": {
|
||||
"type": "iPhone 11 Pro"
|
||||
"type": "iPhone 11 Pro",
|
||||
"os": "13.7"
|
||||
},
|
||||
"artifacts": {
|
||||
"plugins": {
|
||||
|
|
|
@ -111,11 +111,20 @@ stories.add('with icon', () => (
|
|||
</List.Container>
|
||||
));
|
||||
|
||||
stories.add('with custom color', () => (
|
||||
stories.add('with custom colors', () => (
|
||||
<List.Container>
|
||||
<List.Separator />
|
||||
<List.Item title='Chats' color='red' />
|
||||
<List.Separator />
|
||||
<List.Item
|
||||
title='Press me!'
|
||||
color='white'
|
||||
onPress={() => alert('Press')}
|
||||
backgroundColor='red'
|
||||
underlayColor='green'
|
||||
translateTitle={false}
|
||||
/>
|
||||
<List.Separator />
|
||||
</List.Container>
|
||||
));
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions, react/prop-types */
|
||||
import React from 'react';
|
||||
import { storiesOf } from '@storybook/react-native';
|
||||
|
||||
import ServerItemComponent from '../../app/presentation/ServerItem';
|
||||
import { ThemeContext } from '../../app/theme';
|
||||
|
||||
const stories = storiesOf('ServerItem', module);
|
||||
|
||||
const themes = {
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
black: 'black'
|
||||
};
|
||||
|
||||
const item = {
|
||||
name: 'Rocket.Chat',
|
||||
id: 'https://open.rocket.chat/',
|
||||
iconURL: 'https://open.rocket.chat/images/logo/android-chrome-512x512.png'
|
||||
};
|
||||
|
||||
const ServerItem = props => (
|
||||
<ServerItemComponent
|
||||
item={item}
|
||||
hasCheck={false}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
stories.add('content', () => (
|
||||
<>
|
||||
<ServerItem
|
||||
hasCheck
|
||||
/>
|
||||
<ServerItem
|
||||
item={{
|
||||
...item,
|
||||
name: 'Super Long Server Name in Rocket.Chat',
|
||||
id: 'https://superlongservername.tologintoasuperlongservername/'
|
||||
}}
|
||||
/>
|
||||
<ServerItem
|
||||
item={{
|
||||
id: 'https://stable.rocket.chat/'
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
));
|
||||
|
||||
stories.add('touchable', () => (
|
||||
<>
|
||||
<ServerItem onLongPress={() => alert('Long Press')} onPress={() => alert('Press')} />
|
||||
<ServerItem onPress={() => alert('Press')} />
|
||||
<ServerItem onLongPress={() => alert('Long Press')} />
|
||||
</>
|
||||
));
|
||||
|
||||
const ThemeStory = ({ theme }) => (
|
||||
<ThemeContext.Provider value={theme}>
|
||||
<ServerItem
|
||||
theme={theme}
|
||||
hasCheck
|
||||
/>
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
|
||||
stories.add('themes', () => (
|
||||
<>
|
||||
<ThemeStory theme={themes.light} />
|
||||
<ThemeStory theme={themes.dark} />
|
||||
<ThemeStory theme={themes.black} />
|
||||
</>
|
||||
));
|
|
@ -6,6 +6,7 @@ import { storiesOf } from '@storybook/react-native';
|
|||
|
||||
import RoomItem from './RoomItem';
|
||||
import './List';
|
||||
import './ServerItem';
|
||||
import Message from './Message';
|
||||
import UiKitMessage from './UiKitMessage';
|
||||
import UiKitModal from './UiKitModal';
|
||||
|
|
13
yarn.lock
|
@ -13109,9 +13109,9 @@ react-native-text-size@4.0.0-rc.1:
|
|||
resolved "https://registry.yarnpkg.com/react-native-text-size/-/react-native-text-size-4.0.0-rc.1.tgz#1e048d345dd6a5a8e1269e0585c1a5948c478da5"
|
||||
integrity sha512-CysqjU2jK6Yc+a+kEI222pUyTY2ywcU2HqbFqf1KHymW6OPTdvBBHqbEJKL0QiLhQaFYDbqicM+h990s9TP00g==
|
||||
|
||||
react-native-ui-lib@RocketChat/react-native-ui-lib:
|
||||
version "4.2.0"
|
||||
resolved "https://codeload.github.com/RocketChat/react-native-ui-lib/tar.gz/48478a9567c1d5d6ade8def7297578efb04554ca"
|
||||
react-native-ui-lib@RocketChat/react-native-ui-lib#minor-improvements:
|
||||
version "4.2.1"
|
||||
resolved "https://codeload.github.com/RocketChat/react-native-ui-lib/tar.gz/a80f38aaa947849736ce8643253991cdcb639414"
|
||||
dependencies:
|
||||
babel-plugin-transform-inline-environment-variables "^0.0.2"
|
||||
color "^3.1.0"
|
||||
|
@ -13829,10 +13829,9 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
|||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
rn-extensions-share@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/rn-extensions-share/-/rn-extensions-share-2.4.0.tgz#a614f6bf6cdd3948fbd7e0f2519592d4bb5f551f"
|
||||
integrity sha512-zX3HcOhib805fVHR7TMYfFXrVBJWYgcrLYNB89RxbKqmSjaqUyWlHYuF61SKOs/dXXeic91e/L8d1YJa6TdzGA==
|
||||
rn-extensions-share@RocketChat/rn-extensions-share:
|
||||
version "2.4.1"
|
||||
resolved "https://codeload.github.com/RocketChat/rn-extensions-share/tar.gz/4d7c0e4c2f300e4fb116af7b7cc0dbbc8169150c"
|
||||
|
||||
rn-fetch-blob@0.12.0:
|
||||
version "0.12.0"
|
||||
|
|