Merge branch 'develop' into expo-image-picker

This commit is contained in:
Gleidson Daniel Silva 2022-09-14 14:58:18 -03:00 committed by GitHub
commit cc2b78b4fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
156 changed files with 3616 additions and 2714 deletions

View File

@ -156,22 +156,6 @@ module.exports = {
__DEV__: true
},
overrides: [
{
files: ['e2e/**'],
globals: {
by: true,
detox: true,
device: true,
element: true,
expect: true,
waitFor: true
},
rules: {
'import/no-extraneous-dependencies': 0,
'no-await-in-loop': 0,
'no-restricted-syntax': 0
}
},
{
files: ['**/*.ts', '**/*.tsx'],
extends: [
@ -253,6 +237,23 @@ module.exports = {
}
}
}
},
{
files: ['e2e/**'],
globals: {
by: true,
detox: true,
device: true,
element: true,
waitFor: true
},
rules: {
'import/no-extraneous-dependencies': 0,
'no-await-in-loop': 0,
'no-restricted-syntax': 0,
// TODO: remove this rule when update Detox to 20 and test if the namespace Detox is available
'no-undef': 1
}
}
]
};

1
.gitignore vendored
View File

@ -66,5 +66,6 @@ artifacts
e2e/docker/rc_test_env/docker-compose.yml
e2e/docker/data/db
e2e/e2e_account.js
e2e/e2e_account.ts
*.p8

View File

@ -22,6 +22,7 @@ const getStories = () => {
require("../app/containers/Avatar/Avatar.stories.tsx"),
require("../app/containers/BackgroundContainer/index.stories.tsx"),
require("../app/containers/Button/Button.stories.tsx"),
require("../app/containers/Chip/Chip.stories.tsx"),
require("../app/containers/HeaderButton/HeaderButtons.stories.tsx"),
require("../app/containers/List/List.stories.tsx"),
require("../app/containers/LoginServices/LoginServices.stories.tsx"),
@ -38,6 +39,7 @@ const getStories = () => {
require("../app/containers/UIKit/UiKitModal.stories.tsx"),
require("../app/containers/UnreadBadge/UnreadBadge.stories.tsx"),
require("../app/views/CannedResponsesListView/CannedResponseItem.stories.tsx"),
require("../app/views/CreateChannelView/RoomSettings/SwitchItem.stories.tsx"),
require("../app/views/DiscussionsView/Item.stories.tsx"),
require("../app/views/RoomView/LoadMore/LoadMore.stories.tsx"),
require("../app/views/ThreadMessagesView/Item.stories.tsx"),

View File

@ -2,16 +2,16 @@
exports[`Storyshots BackgroundContainer Basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]}]}"`;
exports[`Storyshots BackgroundContainer Black Theme Loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_black\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},\\"color\\":\\"#f9f9f9\\"},\\"children\\":null}]}"`;
exports[`Storyshots BackgroundContainer Black Theme Loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_black\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\"},\\"color\\":\\"#f9f9f9\\"},\\"children\\":null}]}"`;
exports[`Storyshots BackgroundContainer Black Theme Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_black\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#f9f9f9\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
exports[`Storyshots BackgroundContainer Black Theme Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_black\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#f9f9f9\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
exports[`Storyshots BackgroundContainer Dark Theme Loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_dark\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},\\"color\\":\\"#f9f9f9\\"},\\"children\\":null}]}"`;
exports[`Storyshots BackgroundContainer Dark Theme Loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_dark\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\"},\\"color\\":\\"#f9f9f9\\"},\\"children\\":null}]}"`;
exports[`Storyshots BackgroundContainer Dark Theme Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_dark\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#f9f9f9\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
exports[`Storyshots BackgroundContainer Dark Theme Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_dark\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#f9f9f9\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
exports[`Storyshots BackgroundContainer Loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},\\"color\\":\\"#6C727A\\"},\\"children\\":null}]}"`;
exports[`Storyshots BackgroundContainer Loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\"},\\"color\\":\\"#6C727A\\"},\\"children\\":null}]}"`;
exports[`Storyshots BackgroundContainer Long Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
exports[`Storyshots BackgroundContainer Long Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
exports[`Storyshots BackgroundContainer Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Text here\\"]}]}"`;
exports[`Storyshots BackgroundContainer Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Text here\\"]}]}"`;

View File

@ -1,13 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Button Custom Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"purple\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"yellow\\",\\"fontSize\\":18},[{\\"textAlign\\":\\"left\\"}]],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
exports[`Storyshots Button Custom Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"purple\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"yellow\\",\\"fontSize\\":18},[{\\"textAlign\\":\\"left\\"}]],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
exports[`Storyshots Button Disabled Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
exports[`Storyshots Button Disabled Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
exports[`Storyshots Button Disabled Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
exports[`Storyshots Button Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
exports[`Storyshots Button Primary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
exports[`Storyshots Button Primary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
exports[`Storyshots Button Secondary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
exports[`Storyshots Button Secondary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;

View File

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Chip Chip Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Rocket.Cat\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
exports[`Storyshots Chip Chip With Short Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Short\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
exports[`Storyshots Chip Chip Without Avatar 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Avatar\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
exports[`Storyshots Chip Chip Without Avatar And Icon 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":true},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Avatar and Icon\\"]}]}]}]}]}"`;
exports[`Storyshots Chip Chip Without Icon 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityState\\":{\\"disabled\\":true},\\"focusable\\":true,\\"style\\":[{\\"paddingHorizontal\\":8,\\"marginRight\\":8,\\"borderRadius\\":2,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"marginRight\\":8,\\"maxWidth\\":120}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Without Icon\\"]}]}]}]}]}"`;

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots Login Services Separators 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"More options\\"},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Less options\\"},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]"`;
exports[`Storyshots Login Services Separators 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"More options\\"},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":2,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Less options\\"},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]"`;
exports[`Storyshots Login Services Service List 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"github\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"gitlab\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"google\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"apple\\"]}]}]}]"`;
exports[`Storyshots Login Services Service List 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"github\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"gitlab\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"google\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":2,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24},{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"apple\\"]}]}]}]"`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots SearchBox Basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"testID\\":\\"searchbox\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},{\\"margin\\":16,\\"marginBottom\\":16}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,{\\"paddingRight\\":45},{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"Search\\",\\"placeholder\\":\\"Search\\",\\"value\\":\\"\\",\\"blurOnSubmit\\":true,\\"returnKeyType\\":\\"search\\"},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":20,\\"color\\":\\"#2f343d\\"},[{\\"position\\":\\"absolute\\",\\"top\\":14},{\\"right\\":15}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
exports[`Storyshots SearchBox Basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"testID\\":\\"searchbox\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},{\\"margin\\":16,\\"marginBottom\\":16}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,{\\"paddingRight\\":45},{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"Search\\",\\"placeholder\\":\\"Search\\",\\"value\\":\\"\\",\\"blurOnSubmit\\":true,\\"returnKeyType\\":\\"search\\"},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":20,\\"color\\":\\"#2f343d\\"},[{\\"position\\":\\"absolute\\",\\"top\\":14},{\\"right\\":15}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots TextInput Short And Long 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"paddingHorizontal\\":14}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},null]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10,\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"color\\":\\"#0d0e12\\"},null]},\\"children\\":[\\"Short Text\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,null,{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"placeholder\\",\\"placeholder\\":\\"placeholder\\",\\"value\\":\\"Rocket.Chat\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},null]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10,\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"color\\":\\"#0d0e12\\"},null]},\\"children\\":[\\"Long Text\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,null,{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"placeholder\\",\\"placeholder\\":\\"placeholder\\",\\"value\\":\\"https://open.rocket.chat/images/logo/android-chrome-512x512.png\\"},\\"children\\":null}]}]}]}"`;
exports[`Storyshots TextInput Short And Long 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"paddingHorizontal\\":14}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},null]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10,\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"},{\\"color\\":\\"#0d0e12\\"},null]},\\"children\\":[\\"Short Text\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,null,{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"placeholder\\",\\"placeholder\\":\\"placeholder\\",\\"value\\":\\"Rocket.Chat\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},null]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10,\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"},{\\"color\\":\\"#0d0e12\\"},null]},\\"children\\":[\\"Long Text\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,null,{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"underlineColorAndroid\\":\\"transparent\\",\\"accessibilityLabel\\":\\"placeholder\\",\\"placeholder\\":\\"placeholder\\",\\"value\\":\\"https://open.rocket.chat/images/logo/android-chrome-512x512.png\\"},\\"children\\":null}]}]}]}"`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots CollapsibleQuote Item 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"padding\\":10}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"testID\\":\\"collapsibleQuoteTouchable-Engineering (9 today)\\",\\"hitSlop\\":{\\"top\\":4,\\"right\\":4,\\"bottom\\":4,\\"left\\":4},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginTop\\":6,\\"borderWidth\\":1,\\"borderRadius\\":4,\\"minHeight\\":40,\\"backgroundColor\\":\\"#f3f4f5\\",\\"borderLeftColor\\":\\"#CBCED1\\",\\"borderTopColor\\":\\"#e1e5e8\\",\\"borderRightColor\\":\\"#e1e5e8\\",\\"borderBottomColor\\":\\"#e1e5e8\\",\\"borderLeftWidth\\":2,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"borderRadius\\":4,\\"padding\\":8}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Engineering (9 today)\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"width\\":20,\\"height\\":20,\\"right\\":8,\\"top\\":8,\\"justifyContent\\":\\"center\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":22,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}]}"`;
exports[`Storyshots CollapsibleQuote Item 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"padding\\":10}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"testID\\":\\"collapsibleQuoteTouchable-Engineering (9 today)\\",\\"hitSlop\\":{\\"top\\":4,\\"right\\":4,\\"bottom\\":4,\\"left\\":4},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginTop\\":6,\\"borderWidth\\":1,\\"borderRadius\\":4,\\"minHeight\\":40,\\"backgroundColor\\":\\"#f3f4f5\\",\\"borderLeftColor\\":\\"#CBCED1\\",\\"borderTopColor\\":\\"#e1e5e8\\",\\"borderRightColor\\":\\"#e1e5e8\\",\\"borderBottomColor\\":\\"#e1e5e8\\",\\"borderLeftWidth\\":2,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"borderRadius\\":4,\\"padding\\":8}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Engineering (9 today)\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"width\\":20,\\"height\\":20,\\"right\\":8,\\"top\\":8,\\"justifyContent\\":\\"center\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":22,\\"color\\":\\"#6C727A\\"},null,{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}]}"`;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots SwitchItem Switch 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"alignItems\\":\\"flex-start\\",\\"padding\\":16}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"minHeight\\":54,\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"space-between\\",\\"flexDirection\\":\\"row\\",\\"maxHeight\\":80,\\"marginBottom\\":12},{\\"backgroundColor\\":\\"#ffffff\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"marginRight\\":8}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Welcome to Rocket.Chat\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"testID\\":\\"create-channel-switch-id-hint\\",\\"style\\":[{\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"Only authorized users can write new messages\\"]}]},{\\"type\\":\\"RCTSwitch\\",\\"props\\":{\\"testID\\":\\"create-channel-switch-id\\",\\"disabled\\":false,\\"onTintColor\\":\\"#2de0a5\\",\\"style\\":{\\"height\\":31,\\"width\\":51},\\"tintColor\\":\\"#f5455c\\",\\"value\\":false,\\"accessibilityRole\\":\\"switch\\"},\\"children\\":null}]}]}"`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -147,7 +147,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName "4.30.0"
versionName "4.31.0"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -13,8 +13,7 @@ buildscript {
buildToolsVersion = "31.0.0"
minSdkVersion = 23
compileSdkVersion = 31
// TODO: Fix "android:exported" issue and target 31 again
targetSdkVersion = 30
targetSdkVersion = 31
if (System.properties['os.arch'] == "aarch64") {
// For M1 Users we need to use the NDK 24 which added support for aarch64
ndkVersion = "24.0.8215888"
@ -27,8 +26,8 @@ buildscript {
kotlinVersion = '1.6.10'
supportLibVersion = "28.0.0"
libre_build = !(isPlay.toBoolean())
jitsi_url = isPlay ? "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases" : "https://github.com/RocketChat/jitsi-maven-repository/raw/libre/releases"
jitsi_version = isPlay ? "3.6.0" : "3.6.0-libre"
jitsi_url = "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases"
jitsi_version = "3.7.0"
}
repositories {

View File

@ -13,6 +13,7 @@ interface ILoginRequest extends Action {
credentials: any;
logoutOnError?: boolean;
isFromWebView?: boolean;
registerCustomFields?: any;
}
interface ILoginSuccess extends Action {
@ -56,13 +57,15 @@ export type TActionsLogin = ILoginRequest &
export function loginRequest(
credentials: Partial<ICredentials>,
logoutOnError?: boolean,
isFromWebView?: boolean
isFromWebView?: boolean,
registerCustomFields?: any
): ILoginRequest {
return {
type: types.LOGIN.REQUEST,
credentials,
logoutOnError,
isFromWebView
isFromWebView,
registerCustomFields
};
}

View File

@ -0,0 +1,32 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import Chip, { IChip } from './index';
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'flex-start',
padding: 16
}
});
export default {
title: 'Chip'
};
const ChipWrapped = ({ avatar, text, onPress, testID, style }: IChip) => (
<View style={styles.container}>
<Chip avatar={avatar} text={text} onPress={onPress} testID={testID} style={style} />
</View>
);
export const ChipText = () => <ChipWrapped avatar='rocket.cat' text={'Rocket.Cat'} onPress={() => {}} />;
export const ChipWithShortText = () => <ChipWrapped avatar='rocket.cat' text={'Short'} onPress={() => {}} />;
export const ChipWithoutAvatar = () => <ChipWrapped text={'Without Avatar'} onPress={() => {}} />;
export const ChipWithoutIcon = () => <ChipWrapped avatar='rocket.cat' text='Without Icon' />;
export const ChipWithoutAvatarAndIcon = () => <ChipWrapped text='Without Avatar and Icon' />;

View File

@ -0,0 +1,58 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react-native';
import { Provider } from 'react-redux';
import Chip, { IChip } from '.';
import { ISelectedUser } from '../../reducers/selectedUsers';
import { mockedStore as store } from '../../reducers/mockedStore';
const onPressMock = jest.fn((item: any) => item);
const testChip = {
testID: 'test-chip-id',
item: { fname: 'rocket.chat', name: 'rocket.chat' } as ISelectedUser,
onPress: onPressMock
};
const Render = ({ testID, text, avatar, onPress }: IChip) => (
<Provider store={store}>
<Chip testID={testID} text={text} onPress={onPress} avatar={avatar} />
</Provider>
);
describe('Chips', () => {
it('should render the Chip component', () => {
const { findByTestId } = render(
<Render
text={testChip.item.fname}
avatar={testChip.item.name}
testID={testChip.testID}
onPress={() => testChip.onPress(testChip.item)}
/>
);
expect(findByTestId(testChip.testID)).toBeTruthy();
});
it("should not call onPress if it's not passed", async () => {
const { findByTestId } = render(<Render text={testChip.item.fname} avatar={testChip.item.name} testID={testChip.testID} />);
const component = await findByTestId(testChip.testID);
fireEvent.press(component);
expect(onPressMock).not.toHaveBeenCalled();
});
it('should tap Chip and return item', async () => {
const { findByTestId } = render(
<Render
text={testChip.item.fname}
avatar={testChip.item.name}
testID={testChip.testID}
onPress={() => testChip.onPress(testChip.item)}
/>
);
const component = await findByTestId(testChip.testID);
fireEvent.press(component);
expect(onPressMock).toHaveBeenCalled();
expect(onPressMock).toHaveReturnedWith(testChip.item);
});
});

View File

@ -0,0 +1,75 @@
import React from 'react';
import { Pressable, StyleSheet, View, Text, StyleProp, ViewStyle } from 'react-native';
import { useTheme } from '../../theme';
import { CustomIcon } from '../CustomIcon';
import sharedStyles from '../../views/Styles';
import Avatar from '../Avatar';
const styles = StyleSheet.create({
pressable: {
paddingHorizontal: 8,
marginRight: 8,
borderRadius: 2,
justifyContent: 'center',
maxWidth: 192
},
container: {
flexDirection: 'row',
alignItems: 'center'
},
avatar: {
marginRight: 8,
marginVertical: 8
},
textContainer: {
marginRight: 8,
maxWidth: 120
},
name: {
fontSize: 16,
...sharedStyles.textMedium
}
});
export interface IChip {
avatar?: string;
text: string;
onPress?: Function;
testID?: string;
style?: StyleProp<ViewStyle>;
}
const Chip = ({ avatar, text, onPress, testID, style }: IChip) => {
const { colors } = useTheme();
return (
<Pressable
testID={testID}
style={({ pressed }) => [
styles.pressable,
{
backgroundColor: pressed ? colors.bannerBackground : colors.auxiliaryBackground
},
style
]}
disabled={!onPress}
onPress={() => onPress?.()}
android_ripple={{
color: colors.bannerBackground
}}
>
<View style={styles.container}>
{avatar ? <Avatar text={avatar} size={28} style={styles.avatar} /> : null}
<View style={styles.textContainer}>
<Text style={[styles.name, { color: colors.bodyText }]} numberOfLines={1}>
{text}
</Text>
</View>
{onPress ? <CustomIcon name='close' size={16} color={colors.auxiliaryTintColor} /> : null}
</View>
</Pressable>
);
};
export default Chip;

View File

@ -330,7 +330,7 @@ const MessageActions = React.memo(
let options: TActionSheetOptionsItem[] = [];
// Reply
if (!isReadOnly) {
if (!isReadOnly && !tmid) {
options = [
{
title: I18n.t('Reply_in_Thread'),

View File

@ -53,7 +53,7 @@ import {
} from '../../definitions';
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods';
import { hasPermission, debounce, isAndroid, isIOS, isTablet } from '../../lib/methods/helpers';
import { hasPermission, debounce, isAndroid, isIOS, isTablet, compareServerVersion } from '../../lib/methods/helpers';
import { Services } from '../../lib/services';
import { TSupportedThemes } from '../../theme';
import { ChatsStackParamList } from '../../stacks/types';
@ -92,8 +92,8 @@ export interface IMessageBoxProps extends IBaseScreen<ChatsStackParamList & Mast
isActionsEnabled: boolean;
usedCannedResponse: string;
uploadFilePermission: string[];
serverVersion: string;
goToCannedResponses: () => void | null;
serverVersion: string;
}
interface IMessageBoxState {
@ -156,7 +156,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
commandPreview: [],
showCommandPreview: false,
command: {},
tshow: false,
tshow: this.sendThreadToChannel,
mentionLoading: false,
permissionToUpload: true
};
@ -166,6 +166,23 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}
get sendThreadToChannel() {
const { user, serverVersion, tmid } = this.props;
if (tmid && compareServerVersion(serverVersion, 'lowerThan', '5.0.0')) {
return false;
}
if (tmid && user.alsoSendThreadToChannel === 'default') {
return false;
}
if (user.alsoSendThreadToChannel === 'always') {
return true;
}
if (user.alsoSendThreadToChannel === 'never') {
return false;
}
return true;
}
async componentDidMount() {
const db = database.active;
const { rid, tmid, navigation, sharing, usedCannedResponse, isMasterDetail } = this.props;
@ -336,7 +353,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
}
componentDidUpdate(prevProps: IMessageBoxProps) {
const { uploadFilePermission, goToCannedResponses } = this.props;
const { uploadFilePermission, goToCannedResponses, replyWithMention, threadsEnabled } = this.props;
if (prevProps.replyWithMention !== replyWithMention) {
if (threadsEnabled && replyWithMention) {
this.setState({ tshow: this.sendThreadToChannel });
}
}
if (!dequal(prevProps.uploadFilePermission, uploadFilePermission) || prevProps.goToCannedResponses !== goToCannedResponses) {
this.setOptions();
}
@ -642,9 +664,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
};
clearInput = () => {
const { tshow } = this.state;
const { user, serverVersion } = this.props;
this.setInput('');
this.setShowSend(false);
this.setState({ tshow: false });
if (compareServerVersion(serverVersion, 'lowerThan', '5.0.0') || (tshow && user.alsoSendThreadToChannel === 'default')) {
this.setState({ tshow: false });
}
};
canUploadFile = (file: any) => {
@ -928,7 +954,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
// Normal message
} else {
// @ts-ignore
onSubmit(message, undefined, tshow);
onSubmit(message, undefined, tmid ? tshow : false);
}
};
@ -998,7 +1024,12 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
onPress={this.onPressSendToChannel}
testID='messagebox-send-to-channel'
>
<CustomIcon name={tshow ? 'checkbox-checked' : 'checkbox-unchecked'} size={24} color={themes[theme].auxiliaryText} />
<CustomIcon
testID={tshow ? 'send-to-channel-checked' : 'send-to-channel-unchecked'}
name={tshow ? 'checkbox-checked' : 'checkbox-unchecked'}
size={24}
color={themes[theme].auxiliaryText}
/>
<Text style={[styles.sendToChannelText, { color: themes[theme].auxiliaryText }]}>
{I18n.t('Messagebox_Send_to_channel')}
</Text>
@ -1167,7 +1198,8 @@ const mapStateToProps = (state: IApplicationState) => ({
FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList,
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled,
uploadFilePermission: state.permissions['mobile-upload-file']
uploadFilePermission: state.permissions['mobile-upload-file'],
serverVersion: state.server.version
});
const dispatchToProps = {

View File

@ -0,0 +1,16 @@
import React from 'react';
import { RadioButton as RadioButtonUiLib } from 'react-native-ui-lib';
import { useTheme } from '../../theme';
export const RadioButton = ({ check, testID, size }: { check: boolean; testID?: string; size?: number }): React.ReactElement => {
const { colors } = useTheme();
return (
<RadioButtonUiLib
testID={testID}
selected={check}
size={size || 20}
color={check ? colors.tintActive : colors.auxiliaryTintColor}
/>
);
};

View File

@ -0,0 +1,17 @@
import React from 'react';
import { Control, Controller } from 'react-hook-form';
import { FormTextInput, IRCTextInputProps } from './FormTextInput';
interface IControlledFormTextInputProps extends IRCTextInputProps {
control: Control<any>;
name: string;
}
export const ControlledFormTextInput = ({ control, name, ...props }: IControlledFormTextInputProps) => (
<Controller
control={control}
name={name}
render={({ field: { onChange, value } }) => <FormTextInput onChangeText={onChange} value={value} {...props} />}
/>
);

View File

@ -1,2 +1,3 @@
export * from './TextInput';
export * from './FormTextInput';
export * from './ControlledFormTextInput';

View File

@ -1,15 +1,15 @@
import React from 'react';
import { Text } from 'react-native';
import { Text, View } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import FastImage from 'react-native-fast-image';
import { FlatList } from 'react-native-gesture-handler';
import Check from '../../Check';
import * as List from '../../List';
import { textParser } from '../utils';
import styles from './styles';
import { IItemData } from '.';
import { useTheme } from '../../../theme';
import { CustomIcon } from '../../CustomIcon';
interface IItem {
item: IItemData;
@ -30,12 +30,18 @@ const Item = ({ item, selected, onSelect }: IItem) => {
const itemName = item.value?.name || item.text.text.toLowerCase();
const { colors } = useTheme();
return (
<Touchable testID={`multi-select-item-${itemName}`} key={itemName} onPress={() => onSelect(item)} style={[styles.item]}>
<>
{item.imageUrl ? <FastImage style={styles.itemImage} source={{ uri: item.imageUrl }} /> : null}
<Text style={{ color: colors.titleText }}>{textParser([item.text])}</Text>
{selected ? <Check /> : null}
</>
<Touchable testID={`multi-select-item-${itemName}`} key={itemName} onPress={() => onSelect(item)}>
<View style={styles.item}>
<View style={styles.flexZ}>
{item.imageUrl ? <FastImage style={styles.itemImage} source={{ uri: item.imageUrl }} /> : null}
</View>
<View style={styles.flex}>
<Text numberOfLines={1} style={{ color: colors.titleText }}>
{textParser([item.text])}
</Text>
</View>
<View style={styles.flexZ}>{selected ? <CustomIcon color={colors.tintColor} size={22} name='check' /> : null}</View>
</View>
</Touchable>
);
};
@ -43,8 +49,8 @@ const Item = ({ item, selected, onSelect }: IItem) => {
const Items = ({ items, selected, onSelect }: IItems) => (
<FlatList
data={items}
style={[styles.items]}
contentContainerStyle={[styles.itemContent]}
style={styles.items}
contentContainerStyle={styles.itemContent}
keyboardShouldPersistTaps='always'
ItemSeparatorComponent={List.Separator}
keyExtractor={keyExtractor}

View File

@ -65,19 +65,21 @@ export const MultiSelectContent = React.memo(
);
return (
<View style={[styles.actionSheetContainer]}>
<FormTextInput
testID='multi-select-search'
onChangeText={handleSearch}
placeholder={I18n.t('Search')}
inputStyle={{ backgroundColor: colors.focusedBackground }}
bottomSheet={isIOS}
onSubmitEditing={() => {
setTimeout(() => {
hideActionSheet();
}, 150);
}}
/>
<View style={styles.actionSheetContainer}>
<View style={styles.inputStyle}>
<FormTextInput
testID='multi-select-search'
onChangeText={handleSearch}
placeholder={I18n.t('Search')}
inputStyle={{ backgroundColor: colors.focusedBackground }}
bottomSheet={isIOS}
onSubmitEditing={() => {
setTimeout(() => {
hideActionSheet();
}, 150);
}}
/>
</View>
<Items items={items || []} selected={selected} onSelect={onSelect} />
</View>
);

View File

@ -9,7 +9,6 @@ export default StyleSheet.create({
justifyContent: 'flex-end'
},
actionSheetContainer: {
padding: 16,
flex: 1
},
content: {
@ -28,9 +27,9 @@ export default StyleSheet.create({
},
item: {
height: 48,
maxWidth: '85%',
alignItems: 'center',
flexDirection: 'row'
flexDirection: 'row',
flex: 1
},
input: {
minHeight: 48,
@ -46,8 +45,12 @@ export default StyleSheet.create({
right: 16
},
itemContent: {
paddingHorizontal: 16,
paddingBottom: 36
},
inputStyle: {
paddingHorizontal: 16
},
items: {
height: 226
},
@ -83,5 +86,11 @@ export default StyleSheet.create({
borderRadius: 2,
width: 24,
height: 24
},
flex: {
flex: 1
},
flexZ: {
flex: 0
}
});

View File

@ -4,9 +4,8 @@ import { Pressable, StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-n
import Avatar from './Avatar';
import { CustomIcon, TIconsName } from './CustomIcon';
import sharedStyles from '../views/Styles';
import { themes } from '../lib/constants';
import { isIOS } from '../lib/methods/helpers';
import { TSupportedThemes } from '../theme';
import { useTheme } from '../theme';
const styles = StyleSheet.create({
button: {
@ -26,13 +25,9 @@ const styles = StyleSheet.create({
marginRight: 15
},
name: {
fontSize: 17,
fontSize: 16,
...sharedStyles.textMedium
},
username: {
fontSize: 14,
...sharedStyles.textRegular
},
icon: {
marginHorizontal: 15,
alignSelf: 'center'
@ -47,34 +42,35 @@ interface IUserItem {
onLongPress?: () => void;
style?: StyleProp<ViewStyle>;
icon?: TIconsName | null;
theme: TSupportedThemes;
iconColor?: string;
}
const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, theme }: IUserItem) => (
<Pressable
onPress={onPress}
onLongPress={onLongPress}
testID={testID}
android_ripple={{
color: themes[theme].bannerBackground
}}
style={({ pressed }: any) => ({
backgroundColor: isIOS && pressed ? themes[theme].bannerBackground : 'transparent'
})}
>
<View style={[styles.container, styles.button, style]}>
<Avatar text={username} size={30} style={styles.avatar} />
<View style={styles.textContainer}>
<Text style={[styles.name, { color: themes[theme].titleText }]} numberOfLines={1}>
{name}
</Text>
<Text style={[styles.username, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>
@{username}
</Text>
const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, iconColor }: IUserItem) => {
const { colors } = useTheme();
return (
<Pressable
onPress={onPress}
onLongPress={onLongPress}
testID={testID}
android_ripple={{
color: colors.bannerBackground
}}
style={({ pressed }: any) => ({
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'
})}
>
<View style={[styles.container, styles.button, style]}>
<Avatar text={username} size={30} style={styles.avatar} />
<View style={styles.textContainer}>
<Text style={[styles.name, { color: colors.bodyText }]} numberOfLines={1}>
{name}
</Text>
</View>
{icon ? <CustomIcon name={icon} size={22} color={iconColor || colors.actionTintColor} style={styles.icon} /> : null}
</View>
{icon ? <CustomIcon name={icon} size={22} color={themes[theme].actionTintColor} style={styles.icon} /> : null}
</View>
</Pressable>
);
</Pressable>
);
};
export default UserItem;

View File

@ -27,7 +27,7 @@ const styles = StyleSheet.create({
flexShrink: 1,
fontSize: 16,
lineHeight: 22,
...sharedStyles.textMedium
...sharedStyles.textSemibold
},
usernameInfoMessage: {
fontSize: 16,

View File

@ -115,12 +115,14 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
}
}
closeEmojiAndAction = () => {
onPressAction = () => {
const { closeEmojiAndAction } = this.props;
if (closeEmojiAndAction) {
closeEmojiAndAction(this.onPress);
return closeEmojiAndAction(this.onPress);
}
return this.onPress();
};
onPress = debounce(
@ -382,7 +384,7 @@ class MessageContainer extends React.Component<IMessageContainerProps, IMessageC
value={{
user,
baseUrl,
onPress: this.closeEmojiAndAction,
onPress: this.onPressAction,
onLongPress: this.onLongPress,
reactionInit: this.reactionInit,
onErrorPress: this.onErrorPress,

View File

@ -118,7 +118,6 @@ export default StyleSheet.create({
...sharedStyles.textRegular
},
textInfo: {
fontStyle: 'italic',
fontSize: 16,
...sharedStyles.textRegular
},

View File

@ -21,6 +21,7 @@ export interface ILoggedUser {
showMessageInMainThread?: boolean;
isFromWebView?: boolean;
enableMessageParserEarlyAdoption: boolean;
alsoSendThreadToChannel: 'default' | 'always' | 'never';
}
export interface ILoggedUserResultFromServer

View File

@ -117,8 +117,7 @@
"Black": "أسود",
"Block_user": "حظر المستخدم",
"Browser": "المتصفح",
"Broadcast_channel_Description": "يمكن فقط للمستخدمين المصرح لهم كتابة رسائل جديدة، ولكن سيتمكن المستخدمون الآخرون من الرد",
"Broadcast_Channel": "قناة البث",
"Broadcast_hint": "يمكن فقط للمستخدمين المصرح لهم كتابة رسائل جديدة، ولكن سيتمكن المستخدمون الآخرون من الرد",
"Busy": "مشغول",
"By_proceeding_you_are_agreeing": "من خلال المتابعة، أنت توافق على",
"Cancel_editing": "إلغاء التعديل",
@ -389,7 +388,6 @@
"Preferences": "التفضيلات",
"Preferences_saved": "تم حفظ التفضيلات",
"Privacy_Policy": "سياسة الخصوصية",
"Private_Channel": "قناة خاصة",
"Private": "خاص",
"Processing": "جار معالجة...",
"Profile_saved_successfully": "تم حفظ الملف الشخصي بنجاح!",
@ -403,7 +401,6 @@
"Reactions": "التفاعلات",
"Read_External_Permission_Message": "يحتاج Rocket.chat للوصول إلى الصور والملفات الموجودة على الجهاز",
"Read_External_Permission": "صلاحية قراءة الوسائط",
"Read_Only_Channel": "قناة للقراءة فقط",
"Read_Only": "قراءة فقط",
"Read_Receipt": "قراءة المستلم",
"Receive_Group_Mentions": "تلقي إشارات المجموعة",

View File

@ -119,8 +119,7 @@
"Black": "Schwarz",
"Block_user": "Benutzer blockieren",
"Browser": "Browser",
"Broadcast_channel_Description": "Nur autorisierte Benutzer können neue Nachrichten schreiben, die anderen Benutzer können jedoch antworten",
"Broadcast_Channel": "Broadcast-Kanal",
"Broadcast_hint": "Nur autorisierte Benutzer können neue Nachrichten schreiben, die anderen Benutzer können jedoch antworten",
"Busy": "Beschäftigt",
"By_proceeding_you_are_agreeing": "Indem Sie fortfahren, akzeptieren Sie unsere",
"Cancel_editing": "Bearbeitung abbrechen",
@ -394,7 +393,6 @@
"Preferences": "Einstellungen",
"Preferences_saved": "Einstellungen gespeichert!",
"Privacy_Policy": " Datenschutzbestimmungen",
"Private_Channel": "Privater Kanal",
"Private": "Privat",
"Processing": "Bearbeite …",
"Profile_saved_successfully": "Profil erfolgreich gespeichert!",
@ -409,7 +407,6 @@
"Reactions": "Reaktionen",
"Read_External_Permission_Message": "Rocket.Chat benötigt Zugriff auf Ihre Fotos, Medien und Dateien auf Ihrem Gerät",
"Read_External_Permission": "Lese-Zugriff auf Medien",
"Read_Only_Channel": "Nur-Lese-Kanal",
"Read_Only": "Schreibgeschützt",
"Read_Receipt": "Lesebestätigung",
"Receive_Group_Mentions": "Gruppen-Benachrichtigungen erhalten",
@ -705,9 +702,6 @@
"Team_not_found": "Team nicht gefunden",
"Create_Team": "Team erstellen",
"Team_Name": "Team-Name",
"Private_Team": "Privates Team",
"Read_Only_Team": "Nur-Lesen-Team",
"Broadcast_Team": "Broadcast-Team",
"creating_team": "Team erstellen",
"team-name-already-exists": "Ein Team mit diesem Namen existiert bereits",
"Add_Channel_to_Team": "Kanal zum Team hinzufügen",

View File

@ -126,8 +126,6 @@
"Black": "Black",
"Block_user": "Block user",
"Browser": "Browser",
"Broadcast_channel_Description": "Only authorized users can write new messages, but the other users will be able to reply",
"Broadcast_Channel": "Broadcast Channel",
"Busy": "Busy",
"By_proceeding_you_are_agreeing": "By proceeding you are agreeing to our",
"Cancel_editing": "Cancel editing",
@ -356,6 +354,7 @@
"No_mentioned_messages": "No mentioned messages",
"No_pinned_messages": "No pinned messages",
"No_results_found": "No results found",
"No_members_found": "No members found",
"No_starred_messages": "No starred messages",
"No_thread_messages": "No thread messages",
"No_label_provided": "No {{label}} provided.",
@ -407,7 +406,6 @@
"Preferences": "Preferences",
"Preferences_saved": "Preferences saved!",
"Privacy_Policy": " Privacy Policy",
"Private_Channel": "Private Channel",
"Private": "Private",
"Processing": "Processing...",
"Profile_saved_successfully": "Profile saved successfully!",
@ -422,7 +420,6 @@
"Reactions": "Reactions",
"Read_External_Permission_Message": "Rocket.Chat needs to access photos, media, and files on your device",
"Read_External_Permission": "Read Media Permission",
"Read_Only_Channel": "Read Only Channel",
"Read_Only": "Read Only",
"Read_Receipt": "Read Receipt",
"Receive_Group_Mentions": "Receive Group Mentions",
@ -725,9 +722,6 @@
"Team_not_found": "Team not found",
"Create_Team": "Create Team",
"Team_Name": "Team Name",
"Private_Team": "Private Team",
"Read_Only_Team": "Read Only Team",
"Broadcast_Team": "Broadcast Team",
"creating_team": "creating team",
"team-name-already-exists": "A team with that name already exists",
"Add_Channel_to_Team": "Add Channel to Team",
@ -842,5 +836,26 @@
"error-init-video-conf": "Error starting video call",
"totp-invalid": "Code or password invalid",
"Close_Chat": "Close Chat",
"Select_tags": "Select tags"
"Select_tags": "Select tags",
"Skip": "Skip",
"N_Selected_members": "{{n}} selected",
"Broadcast": "Broadcast",
"Broadcast_hint": "Only authorized users can write new messages, but the other users will be able to reply",
"Team_hint_private": "Only invited people can join",
"Team_hint_public": "When disabled, anyone can join the team",
"Team_hint_not_read_only": "All users in this team can write messages",
"Team_hint_encrypted": "End to end encrypted team. Search will not work with encrypted Teams and notifications may not show the messages content.",
"Team_hint_encrypted_not_available": "Only available for private team",
"Channel_hint_private":"Only invited users can access this Channel",
"Channel_hint_public":"Everyone can access this channel",
"Channel_hint_encrypted": "End to end encrypted channel. Search will not work with encrypted channels and notifications may not show the messages content.",
"Channel_hint_not_read_only": "All users in the channel can write new messages",
"Channel_hint_encrypted_not_available": "Not available for Public Channels",
"Read_only_hint":"Only authorized users can write new messages",
"Discussion": "Discussion",
"Channel": "Channel",
"Team": "Team",
"Select_Members": "Select Members",
"Also_send_thread_message_to_channel_behavior": "Also send thread message to channel",
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Allow users to select the Also send to channel behavior"
}

View File

@ -108,8 +108,7 @@
"Back": "Volver",
"Black": "Negro",
"Block_user": "Bloquear usuario",
"Broadcast_channel_Description": "Sólo los usuarios autorizados pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos.",
"Broadcast_Channel": "Canal de Transmisión",
"Broadcast_hint": "Sólo los usuarios autorizados pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos.",
"Busy": "Ocupado",
"By_proceeding_you_are_agreeing": "Al proceder estarás de acuerdo",
"Cancel_editing": "Cancelar edición",
@ -273,7 +272,6 @@
"Preferences": "Preferencias",
"Preferences_saved": "¡Preferencias guardadas!",
"Privacy_Policy": "Política de privacidad",
"Private_Channel": "Canal privado",
"Private": "Privado",
"Processing": "Procesando...",
"Profile_saved_successfully": "¡Perfil guardado correctamente!",
@ -286,7 +284,6 @@
"Reactions_are_disabled": "Las reacciones están desactivadas",
"Reactions_are_enabled": "Las reacciones están activadas",
"Reactions": "Reacciones",
"Read_Only_Channel": "Canal de sólo lectura",
"Read_Only": "Sólo lectura ",
"Read_Receipt": "Comprobante de lectura",
"Receive_Group_Mentions": "Recibir menciones de grupo",

View File

@ -119,8 +119,7 @@
"Black": "Noir",
"Block_user": "Bloquer l'utilisateur",
"Browser": "Navigateur",
"Broadcast_channel_Description": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre.",
"Broadcast_Channel": "Canal de diffusion",
"Broadcast_hint": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre.",
"Busy": "Occupé",
"By_proceeding_you_are_agreeing": "En poursuivant, vous acceptez nos",
"Cancel_editing": "Annuler la modification",
@ -398,7 +397,6 @@
"Preferences": "Préférences",
"Preferences_saved": "Préférences sauvegardées !",
"Privacy_Policy": " Politique de confidentialité",
"Private_Channel": "Canal privé",
"Private": "Privé",
"Processing": "Traitement...",
"Profile_saved_successfully": "Profil enregistré avec succès !",
@ -413,7 +411,6 @@
"Reactions": "Réactions",
"Read_External_Permission_Message": "Rocket.Chat doit accéder aux photos, aux médias et aux fichiers sur votre appareil",
"Read_External_Permission": "Permission de lecture des fichiers",
"Read_Only_Channel": "Canal en lecture seule",
"Read_Only": "Lecture seule",
"Read_Receipt": "Accusé de réception",
"Receive_Group_Mentions": "Recevoir des mentions de groupe",
@ -715,9 +712,6 @@
"Team_not_found": "Equipe non trouvée",
"Create_Team": "Créer une équipe",
"Team_Name": "Nom de l'équipe",
"Private_Team": "Equipe privée",
"Read_Only_Team": "Equipe en lecture seule",
"Broadcast_Team": "Equipe de diffusion",
"creating_team": "création de l'équipe",
"team-name-already-exists": "Une équipe portant ce nom existe déjà",
"Add_Channel_to_Team": "Ajouter un canal à l'équipe",

View File

@ -115,8 +115,7 @@
"Black": "Nero",
"Block_user": "Blocca utente",
"Browser": "Browser",
"Broadcast_channel_Description": "Solo gli utenti autorizzati possono scrivere messaggi, ma gli altri utenti saranno in grado di rispondere",
"Broadcast_Channel": "Canale broadcast",
"Broadcast_hint": "Solo gli utenti autorizzati possono scrivere messaggi, ma gli altri utenti saranno in grado di rispondere",
"Busy": "Occupato",
"By_proceeding_you_are_agreeing": "Procedendo accetti i nostri",
"Cancel_editing": "Annulla modifica",
@ -383,7 +382,6 @@
"Preferences": "Impostazioni",
"Preferences_saved": "Impostazioni salvate!",
"Privacy_Policy": " Privacy Policy",
"Private_Channel": "Canale privato",
"Private": "Privato",
"Processing": "Elaborazione...",
"Profile_saved_successfully": "Profilo salvato correttamente!",
@ -398,7 +396,6 @@
"Reactions": "Reazioni",
"Read_External_Permission_Message": "Rocket.Chat deve accedere alle foto, media, e documenti sul tuo dispositivo",
"Read_External_Permission": "Permesso di Lettura della Memoria",
"Read_Only_Channel": "Canale in sola lettura",
"Read_Only": "Sola lettura",
"Read_Receipt": "Conferma di lettura",
"Receive_Group_Mentions": "Ricevi menzioni di gruppo",

View File

@ -119,8 +119,7 @@
"Black": "ブラック",
"Block_user": "ブロックしたユーザー",
"Browser": "ブラウザ",
"Broadcast_channel_Description": "許可されたユーザーのみが新しいメッセージを書き込めます。他のユーザーは返信することができます",
"Broadcast_Channel": "配信チャンネル",
"Broadcast_hint": "許可されたユーザーのみが新しいメッセージを書き込めます。他のユーザーは返信することができます",
"Busy": "取り込み中",
"By_proceeding_you_are_agreeing": "続行することにより、私達を承認します",
"Cancel_editing": "編集をキャンセル",
@ -371,7 +370,6 @@
"Preferences": "設定",
"Preferences_saved": "設定が保存されました。",
"Privacy_Policy": " プライバシーポリシー",
"Private_Channel": "プライベートチャンネル",
"Private": "プライベート",
"Processing": "処理中...",
"Profile_saved_successfully": "プロフィールが保存されました!",
@ -384,7 +382,6 @@
"Reactions_are_disabled": "リアクションは無効化されています",
"Reactions_are_enabled": "リアクションは有効化されています",
"Reactions": "リアクション",
"Read_Only_Channel": "読み取り専用チャンネル",
"Read_Only": "読み取り専用",
"Read_Receipt": "レシートを見る",
"Receive_Group_Mentions": "グループの通知を受け取る",

View File

@ -119,8 +119,7 @@
"Black": "Zwart",
"Block_user": "Blokkeer gebruiker",
"Browser": "Browser",
"Broadcast_channel_Description": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven, maar de andere gebruikers zullen kunnen antwoorden",
"Broadcast_Channel": "Uitzendkanaal",
"Broadcast_hint": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven, maar de andere gebruikers zullen kunnen antwoorden",
"Busy": "Bezig",
"By_proceeding_you_are_agreeing": "Door verder te gaan ga je akkoord met onze",
"Cancel_editing": "Bewerken annuleren",
@ -398,7 +397,6 @@
"Preferences": "Voorkeuren",
"Preferences_saved": "Voorkeuren opgeslagen!",
"Privacy_Policy": " Privacybeleid",
"Private_Channel": "Privékanaal",
"Private": "Privé",
"Processing": "Verwerking...",
"Profile_saved_successfully": "Profiel succesvol opgeslagen!",
@ -413,7 +411,6 @@
"Reactions": "Reacties",
"Read_External_Permission_Message": "Rocket.Chat heeft toegang nodig tot foto's, media en bestanden op je apparaat",
"Read_External_Permission": "Lees toestemming voor media",
"Read_Only_Channel": "Alleen-lezen kanaal",
"Read_Only": "Alleen lezen",
"Read_Receipt": "Leesbevestiging",
"Receive_Group_Mentions": "Groepsvermeldingen ontvangen",
@ -715,9 +712,6 @@
"Team_not_found": "Team niet gevonden",
"Create_Team": "Team aanmaken",
"Team_Name": "Teamnaam",
"Private_Team": "Privé team",
"Read_Only_Team": "Alleen-lezen team",
"Broadcast_Team": "Broadcast team",
"creating_team": "team maken",
"team-name-already-exists": "Er bestaat al een team met die naam",
"Add_Channel_to_Team": "Kanaal toevoegen aan team",

View File

@ -121,8 +121,6 @@
"Black": "Preto",
"Block_user": "Bloquear usuário",
"Browser": "Navegador",
"Broadcast_channel_Description": "Somente usuários autorizados podem escrever novas mensagens, mas os outros usuários poderão responder",
"Broadcast_Channel": "Canal de Transmissão",
"Busy": "Ocupado",
"By_proceeding_you_are_agreeing": "Ao prosseguir você está aceitando",
"Cancel_editing": "Cancelar edição",
@ -334,6 +332,7 @@
"No_mentioned_messages": "Não há menções",
"No_pinned_messages": "Não há mensagens fixadas",
"No_results_found": "Nenhum resultado encontrado",
"No_members_found": "Nenhum usuário encontrado",
"No_starred_messages": "Não há mensagens favoritas",
"No_thread_messages": "Não há tópicos",
"No_label_provided": "Sem {{label}}.",
@ -383,7 +382,6 @@
"Preferences": "Preferências",
"Preferences_saved": "Preferências salvas!",
"Privacy_Policy": " Política de Privacidade",
"Private_Channel": "Canal Privado",
"Private": "Privado",
"Processing": "Processando...",
"Profile_saved_successfully": "Perfil salvo com sucesso!",
@ -398,7 +396,6 @@
"Reactions": "Reações",
"Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, mídia e arquivos no seu dispositivo",
"Read_External_Permission": "Permissão de acesso à arquivos",
"Read_Only_Channel": "Canal Somente Leitura",
"Read_Only": "Somente Leitura",
"Read_Receipt": "Lida por",
"Receive_Group_Mentions": "Receber menções de grupo",
@ -682,7 +679,6 @@
"Teams": "Times",
"No_team_channels_found": "Nenhum canal encontrado",
"Team_not_found": "Time não encontrado",
"Private_Team": "Equipe Privada",
"Add_Channel_to_Team": "Adicionar Canal ao Time",
"Left_The_Team_Successfully": "Saiu do time com sucesso",
"Create_New": "Criar",
@ -795,5 +791,26 @@
"Show_badge_for_mentions_Info": "Mostrar contador somente para menções diretas",
"totp-invalid": "Código ou senha inválida",
"Close_Chat": "Fechar Conversa",
"Select_tags": "Selecionar tag(s)"
"Select_tags": "Selecionar tag(s)",
"Skip": "Pular",
"N_Selected_members": "{{n}} selecionados",
"Broadcast": "Transmissão",
"Broadcast_hint": "Somente usuários autorizados podem escrever novas mensagens, mas os outros usuários poderão responder",
"Team_hint_private": "Apenas pessoas convidadas podem entrar",
"Team_hint_public": "Quando desativado, qualquer um pode entrar na equipe",
"Team_hint_not_read_only": "Todos os usuários nesta equipe podem escrever mensagens",
"Team_hint_encrypted": "Equipe criptografada de ponta a ponta. A pesquisa não funcionará com equipes criptografadas e as notificações poderão não exibir o conteúdo das mensagens.",
"Team_hint_encrypted_not_available": "Disponível apenas para equipes privadas",
"Channel_hint_private":"Apenas usuários convidados podem acessar este canal",
"Channel_hint_public":"Todos podem acessar este canal",
"Channel_hint_encrypted": "Canal criptografado de ponta a ponta. A pesquisa não funcionará com canais criptografados e as notificações podem não mostrar o conteúdo das mensagens.",
"Channel_hint_not_read_only": "Todos usuários no canal podem enviar mensagens novas",
"Channel_hint_encrypted_not_available": "Indisponível para canais públicos",
"Read_only_hint":"Somente usuários autorizados podem escrever novas mensagens",
"Discussion": "Discussão",
"Channel": "Canal",
"Team": "Time",
"Select_Members": "Selecionar Membros",
"Also_send_thread_message_to_channel_behavior": "Também enviar mensagem do tópico para o canal",
"Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Permitir que os usuários selecionem o comportamento Também enviar para o canal"
}

View File

@ -118,8 +118,7 @@
"Black": "Preto",
"Block_user": "Bloquear utilizador",
"Browser": "Navegador",
"Broadcast_channel_Description": "Apenas utilizadores autorizados podem escrever novas mensagens, mas os outros utilizadores poderão responder",
"Broadcast_Channel": "Canal de Transmissão",
"Broadcast_hint": "Apenas utilizadores autorizados podem escrever novas mensagens, mas os outros utilizadores poderão responder",
"Busy": "Ocupado",
"By_proceeding_you_are_agreeing": "Ao prosseguir você concorda com o(s) nosso(s)",
"Cancel_editing": "Cancelar edição",
@ -390,7 +389,6 @@
"Preferences": "Preferências",
"Preferences_saved": "Preferências guardadas!",
"Privacy_Policy": " Política de Privacidade",
"Private_Channel": "Canal Privado",
"Private": "Privado",
"Processing": "A processar...",
"Profile_saved_successfully": "Perfil actualizado com sucesso!",
@ -405,7 +403,6 @@
"Reactions": "Reacções",
"Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, média e arquivos em seu dispositivo",
"Read_External_Permission": "Permissão de leitura da média",
"Read_Only_Channel": "Canal só de leitura",
"Read_Only": "Só de Leitura",
"Read_Receipt": "Recibos de leitura",
"Register": "Registar",

View File

@ -119,8 +119,7 @@
"Black": "Черный",
"Block_user": "Блокировать пользователя",
"Browser": "Браузер",
"Broadcast_channel_Description": "Только авторизованные пользователи могут писать новые сообщения, но другие пользователи смогут ответить",
"Broadcast_Channel": "Широковещательный канал",
"Broadcast_hint": "Только авторизованные пользователи могут писать новые сообщения, но другие пользователи смогут ответить",
"Busy": "Занят",
"By_proceeding_you_are_agreeing": "Продолжая, вы соглашаетесь с нашими",
"Cancel_editing": "Отменить правку",
@ -394,7 +393,6 @@
"Preferences": "Настройки",
"Preferences_saved": "Настройки сохранены!",
"Privacy_Policy": " Политика конфиденциальности",
"Private_Channel": "Приватный канал",
"Private": "Приватный",
"Processing": "Обработка...",
"Profile_saved_successfully": "Профиль успешно сохранен!",
@ -409,7 +407,6 @@
"Reactions": "Реакции",
"Read_External_Permission_Message": "Rocket.Chat необходим доступ к фотографиям, медиа и другим файлам на вашем устройстве",
"Read_External_Permission": "Разрешение на Чтение Медиа",
"Read_Only_Channel": "Канал только для чтения",
"Read_Only": "Только для чтения",
"Read_Receipt": "Уведомление о прочтении",
"Receive_Group_Mentions": "Получать групповые уведомления",
@ -706,9 +703,6 @@
"Team_not_found": "Команда не найдена",
"Create_Team": "Создать Команду",
"Team_Name": "Имя Команды",
"Private_Team": "Приватная Команда",
"Read_Only_Team": "Команда только для чтения",
"Broadcast_Team": "Широковещательная Команда",
"creating_team": "создание Команды",
"team-name-already-exists": "Команда с таким названием уже существует",
"Add_Channel_to_Team": "Добавить канал в Команду",

View File

@ -115,8 +115,7 @@
"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",
"Broadcast_hint": "Yalnızca yetkili kullanıcılar yeni ileti yazabilir, ancak diğer kullanıcılar yanıt verebilir",
"Busy": "Meşgul",
"By_proceeding_you_are_agreeing": "Devam ederek kabul ediyorsunuz: ",
"Cancel_editing": "Düzenlemeyi iptal et",
@ -384,7 +383,6 @@
"Preferences": "Tercihler",
"Preferences_saved": "Tercihler kaydedildi!",
"Privacy_Policy": " Privacy Policy",
"Private_Channel": "Özel Kanal",
"Private": "Özel",
"Processing": "İşleniyor...",
"Profile_saved_successfully": "Profil başarıyla kaydedildi!",
@ -399,7 +397,6 @@
"Reactions": "Tepkiler",
"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",

View File

@ -115,8 +115,7 @@
"Black": "黑色",
"Block_user": "屏蔽此用户",
"Browser": "浏览器",
"Broadcast_channel_Description": "只有经过授权的用户才能写新信息,但其他用户可以回复",
"Broadcast_Channel": "广播频道",
"Broadcast_hint": "只有经过授权的用户才能写新信息,但其他用户可以回复",
"Busy": "忙碌",
"By_proceeding_you_are_agreeing": "继续操作,请同意我们的",
"Cancel_editing": "取消编辑",
@ -381,7 +380,6 @@
"Preferences": "偏好设置",
"Preferences_saved": "偏好已保存!",
"Privacy_Policy": "隐私政策",
"Private_Channel": "私人频道",
"Private": "私有的",
"Processing": "处理中",
"Profile_saved_successfully": "个人资料保存成功!",
@ -396,7 +394,6 @@
"Reactions": "表情貼",
"Read_External_Permission_Message": "Rocket.Chat 需要存取您装置上的相片、多媒体及文件",
"Read_External_Permission": "读取媒体权限",
"Read_Only_Channel": "只读频道",
"Read_Only": "只读",
"Read_Receipt": "查看已读人员",
"Receive_Group_Mentions": "接收群组提及",

View File

@ -116,8 +116,7 @@
"Black": "黑色",
"Block_user": "封鎖此用戶",
"Browser": "瀏覽器",
"Broadcast_channel_Description": "只有經過授權的使用者才能發送新訊息,但其他使用者可以回覆",
"Broadcast_Channel": "廣播頻道",
"Broadcast_hint": "只有經過授權的使用者才能發送新訊息,但其他使用者可以回覆",
"Busy": "忙碌",
"By_proceeding_you_are_agreeing": "若要繼續操作,請同意我們的",
"Cancel_editing": "取消編輯",
@ -383,7 +382,6 @@
"Preferences": "偏好設定",
"Preferences_saved": "偏好設定已被儲存!",
"Privacy_Policy": "隱私政策",
"Private_Channel": "私人頻道",
"Private": "私有的",
"Processing": "處理中",
"Profile_saved_successfully": "個人資料儲存成功!",
@ -398,7 +396,6 @@
"Reactions": "表情貼",
"Read_External_Permission_Message": "Rocket.Chat 需要存取您裝置上的相片、多媒體及檔案",
"Read_External_Permission": "讀取媒體權限",
"Read_Only_Channel": "唯讀頻道",
"Read_Only": "唯讀",
"Read_Receipt": "查看已讀人員",
"Receive_Group_Mentions": "接收群組提及",

View File

@ -85,7 +85,7 @@ export function hasRole(role): boolean {
return userRoles.indexOf(role) > -1;
}
export async function hasPermission(permissions, rid?: any): boolean[] {
export async function hasPermission(permissions, rid?: any): Promise<boolean[]> {
let roomRoles = [];
if (rid) {
const db = database.active;

View File

@ -272,6 +272,10 @@ export default {
RA_MOVE_TO_TEAM_F: 'ra_move_to_team_f',
RA_SEARCH_TEAM: 'ra_search_team',
// ROOM MEMBERS ACTIONS VIEW
RM_GO_SELECTEDUSERS: 'rm_go_selected_users',
RM_GO_INVITEUSERS: 'rm_go_invite_users',
// ROOM INFO VIEW
RI_GO_RI_EDIT: 'ri_go_ri_edit',
RI_GO_LIVECHAT_EDIT: 'ri_go_livechat_edit',

View File

@ -4,6 +4,7 @@ import { DarkTheme, DefaultTheme } from '@react-navigation/native';
import { themes } from '../../../constants';
import { TSupportedThemes } from '../../../../theme';
import { isIOS } from '../deviceInfo';
import sharedStyles from '../../../../views/Styles';
export * from './animations';
@ -36,7 +37,7 @@ export const themedHeader = (theme: TSupportedThemes) => ({
backgroundColor: themes[theme].headerBackground
},
headerTintColor: themes[theme].headerTintColor,
headerTitleStyle: { color: themes[theme].headerTitleColor }
headerTitleStyle: { ...sharedStyles.textSemibold, color: themes[theme].headerTitleColor, fontSize: 18 }
});
export const navigationTheme = (theme: TSupportedThemes) => {

View File

@ -295,6 +295,9 @@ export default function subscribeRooms() {
if ((['settings.preferences.showMessageInMainThread'] as any) in diff) {
store.dispatch(setUser({ showMessageInMainThread: diff['settings.preferences.showMessageInMainThread'] }));
}
if ((['settings.preferences.alsoSendThreadToChannel'] as any) in diff) {
store.dispatch(setUser({ alsoSendThreadToChannel: diff['settings.preferences.alsoSendThreadToChannel'] }));
}
}
if (/subscriptions/.test(ev)) {
if (type === 'removed') {

View File

@ -17,10 +17,15 @@ function replace(name: string, params: any) {
navigationRef.current?.dispatch(StackActions.replace(name, params));
}
function popToTop() {
navigationRef.current?.dispatch(StackActions.popToTop());
}
export default {
navigationRef,
routeNameRef,
navigate,
back,
replace
replace,
popToTop
};

View File

@ -268,8 +268,10 @@ async function login(credentials: ICredentials, isFromWebView = false): Promise<
const result = sdk.current.currentLogin?.result;
let enableMessageParserEarlyAdoption = true;
let showMessageInMainThread = false;
if (compareServerVersion(serverVersion, 'lowerThan', '5.0.0')) {
enableMessageParserEarlyAdoption = result.me.settings?.preferences?.enableMessageParserEarlyAdoption ?? true;
showMessageInMainThread = result.me.settings?.preferences?.showMessageInMainThread ?? true;
}
if (result) {
@ -287,8 +289,9 @@ async function login(credentials: ICredentials, isFromWebView = false): Promise<
roles: result.me.roles,
avatarETag: result.me.avatarETag,
isFromWebView,
showMessageInMainThread: result.me.settings?.preferences?.showMessageInMainThread ?? true,
enableMessageParserEarlyAdoption
showMessageInMainThread,
enableMessageParserEarlyAdoption,
alsoSendThreadToChannel: result.me.settings?.preferences?.alsoSendThreadToChannel
};
return user;
}

View File

@ -688,7 +688,7 @@ export const runSlashCommand = (command: string, roomId: string, params: string,
roomId,
params,
triggerId,
tmid
...(tmid && { tmid })
});
export const getCommandPreview = (command: string, roomId: string, params: string) =>

View File

@ -42,7 +42,12 @@ const loginWithPasswordCall = args => Services.loginWithPassword(args);
const loginCall = (credentials, isFromWebView) => Services.login(credentials, isFromWebView);
const logoutCall = args => logout(args);
const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnError = false, isFromWebView = false }) {
const handleLoginRequest = function* handleLoginRequest({
credentials,
logoutOnError = false,
isFromWebView = false,
registerCustomFields
}) {
logEvent(events.LOGIN_DEFAULT_LOGIN);
try {
let result;
@ -78,6 +83,10 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE
}
});
yield put(loginSuccess(result));
if (registerCustomFields) {
const updatedUser = yield call(Services.saveUserProfile, {}, { ...registerCustomFields });
yield put(setUser({ ...result, ...updatedUser.user }));
}
}
} catch (e) {
if (e?.data?.message && /you've been logged out by the server/i.test(e.data.message)) {

View File

@ -95,7 +95,7 @@ const ChatsStackNavigator = () => {
<ChatsStack.Screen name='SelectListView' component={SelectListView} options={SelectListView.navigationOptions} />
<ChatsStack.Screen name='RoomInfoView' component={RoomInfoView} options={RoomInfoView.navigationOptions} />
<ChatsStack.Screen name='RoomInfoEditView' component={RoomInfoEditView} options={RoomInfoEditView.navigationOptions} />
<ChatsStack.Screen name='RoomMembersView' component={RoomMembersView} options={RoomMembersView.navigationOptions} />
<ChatsStack.Screen name='RoomMembersView' component={RoomMembersView} />
<ChatsStack.Screen name='DiscussionsView' component={DiscussionsView} />
<ChatsStack.Screen
name='SearchMessagesView'
@ -123,7 +123,7 @@ const ChatsStackNavigator = () => {
options={ThreadMessagesView.navigationOptions}
/>
<ChatsStack.Screen name='TeamChannelsView' component={TeamChannelsView} />
<ChatsStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} />
<ChatsStack.Screen name='CreateChannelView' component={CreateChannelView} />
<ChatsStack.Screen name='AddChannelTeamView' component={AddChannelTeamView} />
<ChatsStack.Screen
name='AddExistingChannelView'
@ -252,13 +252,9 @@ const NewMessageStackNavigator = () => {
<NewMessageStack.Navigator
screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation } as StackNavigationOptions}
>
<NewMessageStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} />
<NewMessageStack.Screen name='NewMessageView' component={NewMessageView} />
<NewMessageStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} />
<NewMessageStack.Screen
name='CreateChannelView'
component={CreateChannelView}
options={CreateChannelView.navigationOptions}
/>
<NewMessageStack.Screen name='CreateChannelView' component={CreateChannelView} />
<NewMessageStack.Screen name='CreateDiscussionView' component={CreateDiscussionView} />
</NewMessageStack.Navigator>
);

View File

@ -127,7 +127,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
<ModalStack.Screen name='RoomInfoView' component={RoomInfoView} options={RoomInfoView.navigationOptions} />
<ModalStack.Screen name='SelectListView' component={SelectListView} />
<ModalStack.Screen name='RoomInfoEditView' component={RoomInfoEditView} options={RoomInfoEditView.navigationOptions} />
<ModalStack.Screen name='RoomMembersView' component={RoomMembersView} options={RoomMembersView.navigationOptions} />
<ModalStack.Screen name='RoomMembersView' component={RoomMembersView} />
<ModalStack.Screen
name='SearchMessagesView'
component={SearchMessagesView}
@ -187,9 +187,9 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
<ModalStack.Screen name='ProfileView' component={ProfileView} />
<ModalStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} />
<ModalStack.Screen name='AdminPanelView' component={AdminPanelView} />
<ModalStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} />
<ModalStack.Screen name='NewMessageView' component={NewMessageView} />
<ModalStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} />
<ModalStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} />
<ModalStack.Screen name='CreateChannelView' component={CreateChannelView} />
<ModalStack.Screen name='CreateDiscussionView' component={CreateDiscussionView} />
<ModalStack.Screen name='E2ESaveYourPasswordView' component={E2ESaveYourPasswordView} />
<ModalStack.Screen name='E2EHowItWorksView' component={E2EHowItWorksView} />

View File

@ -64,6 +64,7 @@ export type ModalStackParamList = {
RoomMembersView: {
rid: string;
room: TSubscriptionModel;
joined?: boolean;
};
DiscussionsView: {
rid: string;

View File

@ -25,7 +25,7 @@ const _OutsideStack = () => {
<Outside.Screen name='NewServerView' component={NewServerView} options={NewServerView.navigationOptions} />
<Outside.Screen name='WorkspaceView' component={WorkspaceView} options={WorkspaceView.navigationOptions} />
<Outside.Screen name='LoginView' component={LoginView} options={LoginView.navigationOptions} />
<Outside.Screen name='ForgotPasswordView' component={ForgotPasswordView} options={ForgotPasswordView.navigationOptions} />
<Outside.Screen name='ForgotPasswordView' component={ForgotPasswordView} />
<Outside.Screen name='SendEmailConfirmationView' component={SendEmailConfirmationView} />
<Outside.Screen name='RegisterView' component={RegisterView} options={RegisterView.navigationOptions} />
<Outside.Screen name='LegalView' component={LegalView} />

View File

@ -75,6 +75,7 @@ export type ChatsStackParamList = {
RoomMembersView: {
rid: string;
room: ISubscription;
joined?: boolean;
};
DiscussionsView: {
rid: string;

View File

@ -1,432 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { FlatList, ScrollView, StyleSheet, Switch, Text, View, SwitchProps } from 'react-native';
import { dequal } from 'dequal';
import * as List from '../containers/List';
import { TextInput } from '../containers/TextInput';
import { sendLoadingEvent } from '../containers/Loading';
import { createChannelRequest } from '../actions/createChannel';
import { removeUser } from '../actions/selectedUsers';
import KeyboardView from '../containers/KeyboardView';
import scrollPersistTaps from '../lib/methods/helpers/scrollPersistTaps';
import I18n from '../i18n';
import UserItem from '../containers/UserItem';
import * as HeaderButton from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
import { SWITCH_TRACK_COLOR, themes } from '../lib/constants';
import { withTheme } from '../theme';
import { Review } from '../lib/methods/helpers/review';
import { getUserSelector } from '../selectors/login';
import { events, logEvent } from '../lib/methods/helpers/log';
import SafeAreaView from '../containers/SafeAreaView';
import sharedStyles from './Styles';
import { ChatsStackParamList } from '../stacks/types';
import { IApplicationState, IBaseScreen, IUser } from '../definitions';
import { hasPermission } from '../lib/methods/helpers';
const styles = StyleSheet.create({
container: {
flex: 1
},
list: {
width: '100%'
},
input: {
height: 54,
paddingHorizontal: 18,
fontSize: 17,
...sharedStyles.textRegular
},
switchContainer: {
height: 54,
alignItems: 'center',
justifyContent: 'space-between',
flexDirection: 'row',
paddingHorizontal: 18
},
label: {
fontSize: 17,
...sharedStyles.textMedium
},
invitedHeader: {
marginTop: 18,
marginHorizontal: 15,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
},
invitedTitle: {
fontSize: 18,
...sharedStyles.textSemibold,
lineHeight: 41
},
invitedCount: {
fontSize: 14,
...sharedStyles.textRegular
}
});
interface IOtherUser {
_id: string;
name: string;
fname: string;
}
interface ICreateChannelViewState {
channelName: string;
type: boolean;
readOnly: boolean;
encrypted: boolean;
broadcast: boolean;
isTeam: boolean;
permissions: boolean[];
}
interface ICreateChannelViewProps extends IBaseScreen<ChatsStackParamList, 'CreateChannelView'> {
baseUrl: string;
error: object;
failure: boolean;
isFetching: boolean;
encryptionEnabled: boolean;
users: IOtherUser[];
user: IUser;
teamId: string;
createPublicChannelPermission: string[] | undefined;
createPrivateChannelPermission: string[] | undefined;
}
interface ISwitch extends SwitchProps {
id: string;
label: string;
}
class CreateChannelView extends React.Component<ICreateChannelViewProps, ICreateChannelViewState> {
private teamId?: string;
constructor(props: ICreateChannelViewProps) {
super(props);
const { route } = this.props;
const isTeam = route?.params?.isTeam || false;
this.teamId = route?.params?.teamId;
this.state = {
channelName: '',
type: true,
readOnly: false,
encrypted: false,
broadcast: false,
isTeam,
permissions: []
};
this.setHeader();
}
componentDidMount() {
this.handleHasPermission();
}
shouldComponentUpdate(nextProps: ICreateChannelViewProps, nextState: ICreateChannelViewState) {
const { channelName, type, readOnly, broadcast, encrypted, permissions } = this.state;
const { users, isFetching, encryptionEnabled, theme, createPublicChannelPermission, createPrivateChannelPermission } =
this.props;
if (nextProps.theme !== theme) {
return true;
}
if (nextState.channelName !== channelName) {
return true;
}
if (nextState.type !== type) {
return true;
}
if (nextState.readOnly !== readOnly) {
return true;
}
if (nextState.encrypted !== encrypted) {
return true;
}
if (nextState.broadcast !== broadcast) {
return true;
}
if (nextState.permissions !== permissions) {
return true;
}
if (nextProps.isFetching !== isFetching) {
return true;
}
if (nextProps.encryptionEnabled !== encryptionEnabled) {
return true;
}
if (!dequal(nextProps.createPublicChannelPermission, createPublicChannelPermission)) {
return true;
}
if (!dequal(nextProps.createPrivateChannelPermission, createPrivateChannelPermission)) {
return true;
}
if (!dequal(nextProps.users, users)) {
return true;
}
return false;
}
componentDidUpdate(prevProps: ICreateChannelViewProps) {
const { createPublicChannelPermission, createPrivateChannelPermission, isFetching } = this.props;
if (
!dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) ||
!dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission)
) {
this.handleHasPermission();
}
if (isFetching !== prevProps.isFetching) {
sendLoadingEvent({ visible: isFetching });
}
}
setHeader = () => {
const { navigation } = this.props;
const { isTeam } = this.state;
navigation.setOptions({
title: isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel')
});
};
toggleRightButton = (channelName: string) => {
const { navigation } = this.props;
navigation.setOptions({
headerRight: () =>
channelName.trim().length > 0 && (
<HeaderButton.Container>
<HeaderButton.Item title={I18n.t('Create')} onPress={this.submit} testID='create-channel-submit' />
</HeaderButton.Container>
)
});
};
onChangeText = (channelName: string) => {
this.toggleRightButton(channelName);
this.setState({ channelName });
};
submit = () => {
const { channelName, type, readOnly, broadcast, encrypted, isTeam } = this.state;
const { users: usersProps, isFetching, dispatch } = this.props;
if (!channelName.trim() || isFetching) {
return;
}
// transform users object into array of usernames
const users = usersProps.map(user => user.name);
// create channel or team
const data = {
name: channelName,
users,
type,
readOnly,
broadcast,
encrypted,
isTeam,
teamId: this.teamId
};
dispatch(createChannelRequest(data));
Review.pushPositiveEvent();
};
removeUser = (user: IOtherUser) => {
logEvent(events.CR_REMOVE_USER);
const { dispatch } = this.props;
dispatch(removeUser(user));
};
renderSwitch = ({ id, value, label, onValueChange, disabled = false }: ISwitch) => {
const { theme } = this.props;
return (
<View style={[styles.switchContainer, { backgroundColor: themes[theme].backgroundColor }]}>
<Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t(label)}</Text>
<Switch
value={value}
onValueChange={onValueChange}
testID={`create-channel-${id}`}
trackColor={SWITCH_TRACK_COLOR}
disabled={disabled}
/>
</View>
);
};
handleHasPermission = async () => {
const { createPublicChannelPermission, createPrivateChannelPermission } = this.props;
const permissions = [createPublicChannelPermission, createPrivateChannelPermission];
const permissionsToCreate = await hasPermission(permissions);
this.setState({ permissions: permissionsToCreate });
};
renderType() {
const { type, isTeam, permissions } = this.state;
const isDisabled = permissions.filter(r => r === true).length <= 1;
return this.renderSwitch({
id: 'type',
value: permissions[1] ? type : false,
disabled: isDisabled,
label: isTeam ? 'Private_Team' : 'Private_Channel',
onValueChange: (value: boolean) => {
logEvent(events.CR_TOGGLE_TYPE);
// If we set the channel as public, encrypted status should be false
this.setState(({ encrypted }) => ({ type: value, encrypted: value && encrypted }));
}
});
}
renderReadOnly() {
const { readOnly, broadcast, isTeam } = this.state;
return this.renderSwitch({
id: 'readonly',
value: readOnly,
label: isTeam ? 'Read_Only_Team' : 'Read_Only_Channel',
onValueChange: value => {
logEvent(events.CR_TOGGLE_READ_ONLY);
this.setState({ readOnly: value });
},
disabled: broadcast
});
}
renderEncrypted() {
const { type, encrypted } = this.state;
const { encryptionEnabled } = this.props;
if (!encryptionEnabled) {
return null;
}
return this.renderSwitch({
id: 'encrypted',
value: encrypted,
label: 'Encrypted',
onValueChange: value => {
logEvent(events.CR_TOGGLE_ENCRYPTED);
this.setState({ encrypted: value });
},
disabled: !type
});
}
renderBroadcast() {
const { broadcast, readOnly, isTeam } = this.state;
return this.renderSwitch({
id: 'broadcast',
value: broadcast,
label: isTeam ? 'Broadcast_Team' : 'Broadcast_Channel',
onValueChange: value => {
logEvent(events.CR_TOGGLE_BROADCAST);
this.setState({
broadcast: value,
readOnly: value ? true : readOnly
});
}
});
}
renderItem = ({ item }: { item: IOtherUser }) => {
const { theme } = this.props;
return (
<UserItem
name={item.fname}
username={item.name}
onPress={() => this.removeUser(item)}
testID={`create-channel-view-item-${item.name}`}
icon='check'
theme={theme}
/>
);
};
renderInvitedList = () => {
const { users, theme } = this.props;
return (
<FlatList
data={users}
extraData={users}
keyExtractor={item => item._id}
style={[
styles.list,
sharedStyles.separatorVertical,
{
backgroundColor: themes[theme].focusedBackground,
borderColor: themes[theme].separatorColor
}
]}
renderItem={this.renderItem}
ItemSeparatorComponent={List.Separator}
keyboardShouldPersistTaps='always'
/>
);
};
render() {
const { channelName, isTeam } = this.state;
const { users, theme } = this.props;
const userCount = users.length;
return (
<KeyboardView
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
contentContainerStyle={[sharedStyles.container, styles.container]}
keyboardVerticalOffset={128}
>
<StatusBar />
<SafeAreaView testID='create-channel-view'>
<ScrollView {...scrollPersistTaps}>
<View style={[sharedStyles.separatorVertical, { borderColor: themes[theme].separatorColor }]}>
<TextInput
autoFocus
style={[styles.input, { backgroundColor: themes[theme].backgroundColor }]}
value={channelName}
onChangeText={this.onChangeText}
placeholder={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
returnKeyType='done'
testID='create-channel-name'
autoCorrect={false}
autoCapitalize='none'
underlineColorAndroid='transparent'
/>
<List.Separator />
{this.renderType()}
<List.Separator />
{this.renderReadOnly()}
<List.Separator />
{this.renderEncrypted()}
<List.Separator />
{this.renderBroadcast()}
</View>
<View style={styles.invitedHeader}>
<Text style={[styles.invitedTitle, { color: themes[theme].titleText }]}>{I18n.t('Invite')}</Text>
<Text style={[styles.invitedCount, { color: themes[theme].auxiliaryText }]}>
{userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })}
</Text>
</View>
{this.renderInvitedList()}
</ScrollView>
</SafeAreaView>
</KeyboardView>
);
}
}
const mapStateToProps = (state: IApplicationState) => ({
baseUrl: state.server.server,
isFetching: state.createChannel.isFetching,
encryptionEnabled: state.encryption.enabled,
users: state.selectedUsers.users,
user: getUserSelector(state),
createPublicChannelPermission: state.permissions['create-c'],
createPrivateChannelPermission: state.permissions['create-p']
});
export default connect(mapStateToProps)(withTheme(CreateChannelView));

View File

@ -0,0 +1,40 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { SwitchItem } from './SwitchItem';
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'flex-start',
padding: 16
}
});
export default {
title: 'SwitchItem'
};
const testSwitch = {
id: 'switch-id',
hint: 'Read_only_hint',
label: 'Onboarding_title',
onValueChange: () => {},
value: false,
testSwitchID: 'create-channel-switch-id',
testLabelID: 'create-channel-switch-id-hint'
};
export const Switch = () => (
<>
<View style={styles.container}>
<SwitchItem
hint={testSwitch.hint}
id={testSwitch.id}
label={testSwitch.label}
onValueChange={() => testSwitch.onValueChange()}
value={testSwitch.value}
/>
</View>
</>
);

View File

@ -0,0 +1,68 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react-native';
import { Provider } from 'react-redux';
import i18n from '../../../i18n';
import { SwitchItem, ISwitch } from './SwitchItem';
import { mockedStore as store } from '../../../reducers/mockedStore';
const onPressMock = jest.fn((value: boolean) => value);
const testSwitch = {
id: 'switch-id',
hint: 'Read_only_hint',
label: 'Onboarding_title',
onValueChange: onPressMock,
value: false,
testSwitchID: 'create-channel-switch-id',
testLabelID: 'create-channel-switch-id-hint'
};
const Render = ({ hint, id, label, onValueChange, value }: ISwitch) => (
<Provider store={store}>
<SwitchItem hint={hint} id={id} label={label} onValueChange={onValueChange} value={value} />
</Provider>
);
describe('SwitchItemEncrypted', () => {
it('should not render the Encrypted Switch component', async () => {
const { findByTestId } = render(
<Render
hint={testSwitch.hint}
id={testSwitch.id}
label={testSwitch.label}
onValueChange={value => testSwitch.onValueChange(value)}
value={testSwitch.value}
/>
);
const component = await findByTestId(testSwitch.testSwitchID);
expect(component).toBeTruthy();
});
it('should change value of switch', async () => {
const { findByTestId } = render(
<Render
hint={testSwitch.hint}
id={testSwitch.id}
label={testSwitch.label}
onValueChange={value => testSwitch.onValueChange(value)}
value={testSwitch.value}
/>
);
const component = await findByTestId(testSwitch.testSwitchID);
fireEvent(component, 'valueChange', { value: true });
expect(onPressMock).toHaveReturnedWith({ value: !testSwitch.value });
});
it('check if hint exists and is the same from testSwitch object', async () => {
const { findByTestId } = render(
<Render
hint={testSwitch.hint}
id={testSwitch.id}
label={testSwitch.label}
onValueChange={value => testSwitch.onValueChange(value)}
value={testSwitch.value}
/>
);
const component = await findByTestId(testSwitch.testLabelID);
expect(component.props.children).toBe(i18n.t(testSwitch.hint));
});
});

View File

@ -0,0 +1,59 @@
import React from 'react';
import { StyleSheet, Switch, Text, View, SwitchProps } from 'react-native';
import I18n from '../../../i18n';
import { SWITCH_TRACK_COLOR } from '../../../lib/constants';
import { useTheme } from '../../../theme';
import sharedStyles from '../../Styles';
const styles = StyleSheet.create({
switchContainer: {
minHeight: 54,
alignItems: 'center',
justifyContent: 'space-between',
flexDirection: 'row',
maxHeight: 80,
marginBottom: 12
},
switchTextContainer: {
flex: 1,
marginRight: 8
},
label: {
fontSize: 14,
...sharedStyles.textMedium
},
hint: {
fontSize: 14,
...sharedStyles.textRegular
}
});
export interface ISwitch extends SwitchProps {
id: string;
label: string;
hint: string;
onValueChange: (value: boolean) => void;
}
export const SwitchItem = ({ id, value, label, hint, onValueChange, disabled = false }: ISwitch) => {
const { colors } = useTheme();
return (
<View style={[styles.switchContainer, { backgroundColor: colors.backgroundColor }]}>
<View style={styles.switchTextContainer}>
<Text style={[styles.label, { color: colors.titleText }]}>{I18n.t(label)}</Text>
<Text testID={`create-channel-${id}-hint`} style={[styles.hint, { color: colors.auxiliaryText }]}>
{I18n.t(hint)}
</Text>
</View>
<Switch
value={value}
onValueChange={onValueChange}
testID={`create-channel-${id}`}
trackColor={SWITCH_TRACK_COLOR}
disabled={disabled}
/>
</View>
);
};

View File

@ -0,0 +1,108 @@
import React from 'react';
import { fireEvent, render } from '@testing-library/react-native';
import { Provider } from 'react-redux';
import { SwitchItemEncrypted, ISwitchItemEncrypted } from './SwitchItemEncrypted';
import { mockedStore as store } from '../../../reducers/mockedStore';
import i18n from '../../../i18n';
const onPressMock = jest.fn((value: boolean) => value);
const testEncrypted = {
encrypted: false,
encryptionEnabled: false,
isTeam: false,
onValueChangeEncrypted: onPressMock,
type: false,
testSwitchID: 'create-channel-encrypted',
testLabelID: `create-channel-encrypted-hint`
};
const Render = ({ encrypted, encryptionEnabled, isTeam, onValueChangeEncrypted, type }: ISwitchItemEncrypted) => (
<Provider store={store}>
<SwitchItemEncrypted
encrypted={encrypted}
encryptionEnabled={encryptionEnabled}
isTeam={isTeam}
onValueChangeEncrypted={onValueChangeEncrypted}
type={type}
/>
</Provider>
);
describe('SwitchItemEncrypted', () => {
it('should not render the Encrypted Switch component', async () => {
const { findByTestId } = render(
<Render
encrypted={testEncrypted.encrypted}
encryptionEnabled={testEncrypted.encryptionEnabled}
isTeam={testEncrypted.isTeam}
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
type={testEncrypted.type}
/>
);
try {
await findByTestId(testEncrypted.testSwitchID);
} catch (e) {
expect(e).toBeTruthy();
}
});
it('should render the Encrypted Switch component', async () => {
testEncrypted.encryptionEnabled = true;
const { findByTestId } = render(
<Render
encrypted={testEncrypted.encrypted}
encryptionEnabled={testEncrypted.encryptionEnabled}
isTeam={testEncrypted.isTeam}
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
type={testEncrypted.type}
/>
);
const component = await findByTestId(testEncrypted.testSwitchID);
expect(component).toBeTruthy();
});
it('should change value of switch', async () => {
const { findByTestId } = render(
<Render
encrypted={testEncrypted.encrypted}
encryptionEnabled={testEncrypted.encryptionEnabled}
isTeam={testEncrypted.isTeam}
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
type={testEncrypted.type}
/>
);
const component = await findByTestId(testEncrypted.testSwitchID);
fireEvent(component, 'valueChange', { value: true });
expect(onPressMock).toHaveReturnedWith({ value: !testEncrypted.encrypted });
});
it('label when encrypted and isTeam are false and is a public channel', async () => {
const { findByTestId } = render(
<Render
encrypted={testEncrypted.encrypted}
encryptionEnabled={testEncrypted.encryptionEnabled}
isTeam={testEncrypted.isTeam}
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
type={testEncrypted.type}
/>
);
const component = await findByTestId(testEncrypted.testLabelID);
expect(component.props.children).toBe(i18n.t('Channel_hint_encrypted_not_available'));
});
it('label when encrypted and isTeam are true and is a private team', async () => {
testEncrypted.isTeam = true;
testEncrypted.type = true;
testEncrypted.encrypted = true;
const { findByTestId } = render(
<Render
encrypted={testEncrypted.encrypted}
encryptionEnabled={testEncrypted.encryptionEnabled}
isTeam={testEncrypted.isTeam}
onValueChangeEncrypted={value => testEncrypted.onValueChangeEncrypted(value)}
type={testEncrypted.type}
/>
);
const component = await findByTestId(testEncrypted.testLabelID);
expect(component.props.children).toBe(i18n.t('Team_hint_encrypted'));
});
});

View File

@ -0,0 +1,48 @@
import React from 'react';
import { SwitchItem } from './SwitchItem';
export interface ISwitchItemEncrypted {
encryptionEnabled: boolean;
isTeam: boolean;
type: boolean;
encrypted: boolean;
onValueChangeEncrypted: (value: boolean) => void;
}
export const SwitchItemEncrypted = ({
encryptionEnabled,
isTeam,
type,
encrypted,
onValueChangeEncrypted
}: ISwitchItemEncrypted) => {
if (!encryptionEnabled) {
return null;
}
let hint = '';
if (isTeam && type) {
hint = 'Team_hint_encrypted';
}
if (isTeam && !type) {
hint = 'Team_hint_encrypted_not_available';
}
if (!isTeam && type) {
hint = 'Channel_hint_encrypted';
}
if (!isTeam && !type) {
hint = 'Channel_hint_encrypted_not_available';
}
return (
<SwitchItem
id={'encrypted'}
value={encrypted}
label={'Encrypted'}
hint={hint}
onValueChange={onValueChangeEncrypted}
disabled={!type}
/>
);
};

View File

@ -0,0 +1,37 @@
import React from 'react';
import { SwitchItem } from './SwitchItem';
export const SwitchItemReadOnly = ({
readOnly,
isTeam,
onValueChangeReadOnly,
broadcast
}: {
readOnly: boolean;
isTeam: boolean;
onValueChangeReadOnly: (value: boolean) => void;
broadcast: boolean;
}) => {
let hint = '';
if (readOnly) {
hint = 'Read_only_hint';
}
if (isTeam && !readOnly) {
hint = 'Team_hint_not_read_only';
}
if (!isTeam && !readOnly) {
hint = 'Channel_hint_not_read_only';
}
return (
<SwitchItem
id={'readonly'}
value={readOnly}
label={'Read_Only'}
hint={hint}
onValueChange={onValueChangeReadOnly}
disabled={broadcast}
/>
);
};

View File

@ -0,0 +1,33 @@
import React from 'react';
import { SwitchItem } from './SwitchItem';
export const SwitchItemType = ({
isTeam,
type,
onValueChangeType,
isDisabled
}: {
isTeam: boolean;
type: boolean;
onValueChangeType: (value: boolean) => void;
isDisabled: boolean;
}) => {
let hint = '';
if (isTeam && type) {
hint = 'Team_hint_private';
}
if (isTeam && !type) {
hint = 'Team_hint_public';
}
if (!isTeam && type) {
hint = 'Channel_hint_private';
}
if (!isTeam && !type) {
hint = 'Channel_hint_public';
}
return (
<SwitchItem id={'type'} value={type} disabled={isDisabled} label={'Private'} hint={hint} onValueChange={onValueChangeType} />
);
};

View File

@ -0,0 +1,96 @@
import React, { useCallback, useState } from 'react';
import { UseFormSetValue } from 'react-hook-form';
import { useAppSelector } from '../../../lib/hooks';
import { events, logEvent } from '../../../lib/methods/helpers/log';
import { SwitchItem } from './SwitchItem';
import { SwitchItemType } from './SwitchItemType';
import { SwitchItemReadOnly } from './SwitchItemReadOnly';
import { SwitchItemEncrypted } from './SwitchItemEncrypted';
import { IFormData } from '..';
export const RoomSettings = ({
isTeam,
setValue,
createChannelPermission,
createPrivateChannelPermission
}: {
isTeam: boolean;
setValue: UseFormSetValue<IFormData>;
createChannelPermission: boolean;
createPrivateChannelPermission: boolean;
}) => {
const [type, setType] = useState(true);
const [readOnly, setReadOnly] = useState(false);
const [encrypted, setEncrypted] = useState(false);
const [broadcast, setBroadcast] = useState(false);
const { encryptionEnabled } = useAppSelector(state => ({
encryptionEnabled: state.encryption.enabled
}));
const onValueChangeType = useCallback(
(value: boolean) => {
logEvent(events.CR_TOGGLE_TYPE);
// If we set the channel as public, encrypted status should be false
setType(value);
setValue('type', value);
setEncrypted(value && encrypted);
setValue('encrypted', value && encrypted);
},
[encrypted]
);
const onValueChangeReadOnly = useCallback((value: boolean) => {
logEvent(events.CR_TOGGLE_READ_ONLY);
setReadOnly(value);
setValue('readOnly', value);
}, []);
const onValueChangeEncrypted = useCallback((value: boolean) => {
logEvent(events.CR_TOGGLE_ENCRYPTED);
setEncrypted(value);
setValue('encrypted', value);
}, []);
const onValueChangeBroadcast = (value: boolean) => {
logEvent(events.CR_TOGGLE_BROADCAST);
setBroadcast(value);
setValue('broadcast', value);
setReadOnly(value ? true : readOnly);
setValue('readOnly', value ? true : readOnly);
};
const isDisabled = [createChannelPermission, createPrivateChannelPermission].filter(r => r === true).length <= 1;
return (
<>
<SwitchItemType
isTeam={isTeam}
type={createPrivateChannelPermission ? type : false}
onValueChangeType={onValueChangeType}
isDisabled={isDisabled}
/>
<SwitchItemReadOnly
broadcast={broadcast}
isTeam={isTeam}
readOnly={readOnly}
onValueChangeReadOnly={onValueChangeReadOnly}
/>
<SwitchItemEncrypted
encryptionEnabled={encryptionEnabled}
isTeam={isTeam}
type={type}
encrypted={encrypted}
onValueChangeEncrypted={onValueChangeEncrypted}
/>
<SwitchItem
id={'broadcast'}
value={broadcast}
label={'Broadcast'}
hint={'Broadcast_hint'}
onValueChange={onValueChangeBroadcast}
/>
</>
);
};

View File

@ -0,0 +1,212 @@
import React, { useCallback, useEffect, useLayoutEffect } from 'react';
import { shallowEqual, useDispatch } from 'react-redux';
import { FlatList, ScrollView, StyleSheet, Text, View } from 'react-native';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { useForm } from 'react-hook-form';
import { useAppSelector, usePermissions } from '../../lib/hooks';
import { sendLoadingEvent } from '../../containers/Loading';
import { createChannelRequest } from '../../actions/createChannel';
import { removeUser as removeUserAction } from '../../actions/selectedUsers';
import KeyboardView from '../../containers/KeyboardView';
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
import I18n from '../../i18n';
import StatusBar from '../../containers/StatusBar';
import { useTheme } from '../../theme';
import { Review } from '../../lib/methods/helpers/review';
import SafeAreaView from '../../containers/SafeAreaView';
import sharedStyles from '../Styles';
import { ChatsStackParamList } from '../../stacks/types';
import Button from '../../containers/Button';
import { ControlledFormTextInput } from '../../containers/TextInput';
import Chip from '../../containers/Chip';
import { RoomSettings } from './RoomSettings';
import { ISelectedUser } from '../../reducers/selectedUsers';
const styles = StyleSheet.create({
container: {
flex: 1
},
containerTextInput: {
paddingHorizontal: 16,
marginTop: 16
},
containerStyle: {
marginBottom: 28
},
list: {
width: '100%'
},
invitedHeader: {
marginVertical: 12,
marginHorizontal: 16,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
},
invitedCount: {
fontSize: 12,
...sharedStyles.textRegular
},
invitedList: {
paddingHorizontal: 16
},
buttonCreate: {
marginHorizontal: 16,
marginTop: 24
}
});
export interface IFormData {
channelName: string;
type: boolean;
readOnly: boolean;
encrypted: boolean;
broadcast: boolean;
}
const CreateChannelView = () => {
const [createChannelPermission, createPrivateChannelPermission] = usePermissions(['create-c', 'create-p']);
const {
control,
handleSubmit,
formState: { isDirty },
setValue
} = useForm<IFormData>({
defaultValues: { channelName: '', broadcast: false, encrypted: false, readOnly: false, type: createPrivateChannelPermission }
});
const navigation = useNavigation<StackNavigationProp<ChatsStackParamList, 'CreateChannelView'>>();
const { params } = useRoute<RouteProp<ChatsStackParamList, 'CreateChannelView'>>();
const isTeam = params?.isTeam || false;
const teamId = params?.teamId;
const { colors } = useTheme();
const dispatch = useDispatch();
const { isFetching, useRealName, users } = useAppSelector(
state => ({
isFetching: state.createChannel.isFetching,
users: state.selectedUsers.users,
useRealName: state.settings.UI_Use_Real_Name as boolean
}),
shallowEqual
);
useEffect(() => {
sendLoadingEvent({ visible: isFetching });
}, [isFetching]);
useLayoutEffect(() => {
navigation.setOptions({
title: isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel')
});
}, [isTeam, navigation]);
const removeUser = useCallback(
(user: ISelectedUser) => {
dispatch(removeUserAction(user));
},
[dispatch]
);
const submit = ({ channelName, broadcast, encrypted, readOnly, type }: IFormData) => {
if (!channelName.trim() || isFetching) {
return;
}
// transform users object into array of usernames
const usersMapped = users.map(user => user.name);
// create channel or team
const data = {
name: channelName,
users: usersMapped,
type,
readOnly,
broadcast,
encrypted,
isTeam,
teamId
};
dispatch(createChannelRequest(data));
Review.pushPositiveEvent();
};
return (
<KeyboardView
style={{ backgroundColor: colors.backgroundColor }}
contentContainerStyle={[sharedStyles.container, styles.container]}
keyboardVerticalOffset={128}
>
<StatusBar />
<SafeAreaView style={{ backgroundColor: colors.backgroundColor }} testID='create-channel-view'>
<ScrollView {...scrollPersistTaps}>
<View style={[styles.containerTextInput, { borderColor: colors.separatorColor }]}>
<ControlledFormTextInput
label={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
testID='create-channel-name'
returnKeyType='done'
containerStyle={styles.containerStyle}
name={'channelName'}
control={control}
/>
<RoomSettings
createChannelPermission={createChannelPermission}
createPrivateChannelPermission={createPrivateChannelPermission}
isTeam={isTeam}
setValue={setValue}
/>
</View>
{users.length > 0 ? (
<>
<View style={styles.invitedHeader}>
<Text style={[styles.invitedCount, { color: colors.auxiliaryText }]}>
{I18n.t('N_Selected_members', { n: users.length })}
</Text>
</View>
<FlatList
data={users}
extraData={users}
keyExtractor={item => item._id}
style={[
styles.list,
{
backgroundColor: colors.backgroundColor,
borderColor: colors.separatorColor
}
]}
contentContainerStyle={styles.invitedList}
renderItem={({ item }) => {
const name = useRealName && item.fname ? item.fname : item.name;
const username = item.name;
return (
<Chip
text={name}
avatar={username}
onPress={() => removeUser(item)}
testID={`create-channel-view-item-${item.name}`}
/>
);
}}
keyboardShouldPersistTaps='always'
horizontal
/>
</>
) : null}
<Button
title={isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel')}
type='primary'
onPress={handleSubmit(submit)}
disabled={!isDirty}
testID='create-channel-submit'
loading={isFetching}
style={styles.buttonCreate}
/>
</ScrollView>
</SafeAreaView>
</KeyboardView>
);
};
export default CreateChannelView;

View File

@ -1,75 +1,58 @@
import React from 'react';
import React, { useLayoutEffect, useState } from 'react';
import { Text } from 'react-native';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
import Button from '../containers/Button';
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
import { FormTextInput } from '../containers/TextInput';
import { ControlledFormTextInput } from '../containers/TextInput';
import I18n from '../i18n';
import { themes } from '../lib/constants';
import { Services } from '../lib/services';
import { OutsideParamList } from '../stacks/types';
import { withTheme } from '../theme';
import { showErrorAlert, isValidEmail } from '../lib/methods/helpers';
import { useTheme } from '../theme';
import { showErrorAlert } from '../lib/methods/helpers';
import { events, logEvent } from '../lib/methods/helpers/log';
import { IBaseScreen } from '../definitions';
import sharedStyles from './Styles';
interface IForgotPasswordViewState {
const schema = yup.object().shape({
email: yup.string().email().required()
});
interface ISubmit {
email: string;
invalidEmail: boolean;
isFetching: boolean;
}
type IForgotPasswordViewProps = IBaseScreen<OutsideParamList, 'ForgotPasswordView'>;
const ForgotPasswordView = (): React.ReactElement => {
const {
control,
handleSubmit,
formState: { isValid }
} = useForm<ISubmit>({ mode: 'onChange', resolver: yupResolver(schema) });
class ForgotPasswordView extends React.Component<IForgotPasswordViewProps, IForgotPasswordViewState> {
static navigationOptions = ({ route }: IForgotPasswordViewProps) => ({
title: route.params?.title ?? 'Rocket.Chat'
});
const [isFetching, setIsFetching] = useState(false);
state = {
email: '',
invalidEmail: true,
isFetching: false
};
const navigation = useNavigation<StackNavigationProp<OutsideParamList, 'ForgotPasswordView'>>();
const { params } = useRoute<RouteProp<OutsideParamList, 'ForgotPasswordView'>>();
const { colors } = useTheme();
shouldComponentUpdate(nextProps: IForgotPasswordViewProps, nextState: IForgotPasswordViewState) {
const { email, invalidEmail, isFetching } = this.state;
const { theme } = this.props;
if (nextProps.theme !== theme) {
return true;
}
if (nextState.email !== email) {
return true;
}
if (nextState.invalidEmail !== invalidEmail) {
return true;
}
if (nextState.isFetching !== isFetching) {
return true;
}
return false;
}
useLayoutEffect(() => {
navigation.setOptions({
title: params?.title ?? 'Rocket.Chat'
});
}, [navigation, params?.title]);
validate = (email: string) => {
if (!isValidEmail(email)) {
this.setState({ invalidEmail: true });
return;
}
this.setState({ email, invalidEmail: false });
};
resetPassword = async () => {
logEvent(events.FP_FORGOT_PASSWORD);
const { email, invalidEmail } = this.state;
if (invalidEmail || !email) {
const resetPassword = async ({ email }: ISubmit) => {
if (!isValid) {
return;
}
try {
this.setState({ isFetching: true });
logEvent(events.FP_FORGOT_PASSWORD);
setIsFetching(true);
const result = await Services.forgotPassword(email);
if (result.success) {
const { navigation } = this.props;
navigation.pop();
showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert'));
}
@ -78,41 +61,38 @@ class ForgotPasswordView extends React.Component<IForgotPasswordViewProps, IForg
const msg = (e.data && e.data.error) || I18n.t('There_was_an_error_while_action', { action: I18n.t('resetting_password') });
showErrorAlert(msg, I18n.t('Alert'));
}
this.setState({ isFetching: false });
setIsFetching(false);
};
render() {
const { invalidEmail, isFetching } = this.state;
const { theme } = this.props;
return (
<FormContainer testID='forgot-password-view'>
<FormContainerInner>
<Text style={[sharedStyles.loginTitle, sharedStyles.textBold, { color: colors.titleText }]}>
{I18n.t('Forgot_password')}
</Text>
<ControlledFormTextInput
name='email'
control={control}
autoFocus
placeholder={I18n.t('Email')}
keyboardType='email-address'
returnKeyType='send'
iconLeft='mail'
onSubmitEditing={handleSubmit(resetPassword)}
testID='forgot-password-view-email'
containerStyle={sharedStyles.inputLastChild}
/>
<Button
title={I18n.t('Reset_password')}
type='primary'
onPress={handleSubmit(resetPassword)}
testID='forgot-password-view-submit'
loading={isFetching}
disabled={!isValid}
/>
</FormContainerInner>
</FormContainer>
);
};
return (
<FormContainer testID='forgot-password-view'>
<FormContainerInner>
<Text style={[sharedStyles.loginTitle, sharedStyles.textBold, { color: themes[theme].titleText }]}>
{I18n.t('Forgot_password')}
</Text>
<FormTextInput
autoFocus
placeholder={I18n.t('Email')}
keyboardType='email-address'
returnKeyType='send'
onChangeText={email => this.validate(email)}
onSubmitEditing={this.resetPassword}
testID='forgot-password-view-email'
containerStyle={sharedStyles.inputLastChild}
/>
<Button
title={I18n.t('Reset_password')}
type='primary'
onPress={this.resetPassword}
testID='forgot-password-view-submit'
loading={isFetching}
disabled={invalidEmail}
/>
</FormContainerInner>
</FormContainer>
);
}
}
export default withTheme(ForgotPasswordView);
export default ForgotPasswordView;

View File

@ -1,5 +1,5 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { BackHandler, StyleSheet } from 'react-native';
import JitsiMeet, { JitsiMeetView as RNJitsiMeetView } from 'react-native-jitsi-meet';
import BackgroundTimer from 'react-native-background-timer';
import { connect } from 'react-redux';
@ -69,6 +69,7 @@ class JitsiMeetView extends React.Component<IJitsiMeetViewProps, IJitsiMeetViewS
}
}, 1000);
}
BackHandler.addEventListener('hardwareBackPress', this.endCall);
}
componentWillUnmount() {
@ -78,9 +79,17 @@ class JitsiMeetView extends React.Component<IJitsiMeetViewProps, IJitsiMeetViewS
this.jitsiTimeout = null;
BackgroundTimer.stopBackgroundTimer();
}
JitsiMeet.endCall();
BackHandler.removeEventListener('hardwareBackPress', this.endCall);
if (isIOS) {
JitsiMeet.endCall();
}
}
endCall = () => {
JitsiMeet.endCall();
return null;
};
onConferenceWillJoin = () => {
this.setState({ loading: false });
};

View File

@ -1,336 +0,0 @@
import { Q } from '@nozbe/watermelondb';
import { StackNavigationOptions } from '@react-navigation/stack';
import { dequal } from 'dequal';
import React from 'react';
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { connect } from 'react-redux';
import { createChannelRequest } from '../actions/createChannel';
import { themes } from '../lib/constants';
import * as HeaderButton from '../containers/HeaderButton';
import * as List from '../containers/List';
import SafeAreaView from '../containers/SafeAreaView';
import SearchBox from '../containers/SearchBox';
import StatusBar from '../containers/StatusBar';
import { IApplicationState, IBaseScreen, ISearch, TSubscriptionModel } from '../definitions';
import I18n from '../i18n';
import database from '../lib/database';
import { CustomIcon, TIconsName } from '../containers/CustomIcon';
import Navigation from '../lib/navigation/appNavigation';
import UserItem from '../containers/UserItem';
import { withTheme } from '../theme';
import { goRoom, TGoRoomItem } from '../lib/methods/helpers/goRoom';
import log, { events, logEvent } from '../lib/methods/helpers/log';
import Touch from '../containers/Touch';
import sharedStyles from './Styles';
import { NewMessageStackParamList } from '../stacks/types';
import { search } from '../lib/methods';
import { hasPermission, compareServerVersion } from '../lib/methods/helpers';
const QUERY_SIZE = 50;
const styles = StyleSheet.create({
button: {
height: 46,
flexDirection: 'row',
alignItems: 'center'
},
buttonIcon: {
marginLeft: 18,
marginRight: 16
},
buttonText: {
fontSize: 17,
...sharedStyles.textRegular
},
buttonContainer: {
paddingBottom: 16
}
});
interface IButton {
onPress: () => void;
testID: string;
title: string;
icon: TIconsName;
first?: boolean;
}
interface INewMessageViewState {
search: (ISearch | TSubscriptionModel)[];
chats: TSubscriptionModel[];
permissions: boolean[];
}
interface INewMessageViewProps extends IBaseScreen<NewMessageStackParamList, 'NewMessageView'> {
maxUsers: number;
isMasterDetail: boolean;
serverVersion: string;
createTeamPermission?: string[];
createDirectMessagePermission?: string[];
createPublicChannelPermission?: string[];
createPrivateChannelPermission?: string[];
createDiscussionPermission?: string[];
}
class NewMessageView extends React.Component<INewMessageViewProps, INewMessageViewState> {
static navigationOptions = ({ navigation }: INewMessageViewProps): StackNavigationOptions => ({
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='new-message-view-close' />,
title: I18n.t('New_Message')
});
constructor(props: INewMessageViewProps) {
super(props);
this.init();
this.state = {
search: [],
chats: [],
permissions: []
};
}
// eslint-disable-next-line react/sort-comp
init = async () => {
try {
const db = database.active;
const chats = await db
.get('subscriptions')
.query(Q.where('t', 'd'), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc))
.fetch();
this.setState({ chats });
} catch (e) {
log(e);
}
};
componentDidMount() {
this.handleHasPermission();
}
componentDidUpdate(prevProps: INewMessageViewProps) {
const {
createTeamPermission,
createPublicChannelPermission,
createPrivateChannelPermission,
createDirectMessagePermission,
createDiscussionPermission
} = this.props;
if (
!dequal(createTeamPermission, prevProps.createTeamPermission) ||
!dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) ||
!dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission) ||
!dequal(createDirectMessagePermission, prevProps.createDirectMessagePermission) ||
!dequal(createDiscussionPermission, prevProps.createDiscussionPermission)
) {
this.handleHasPermission();
}
}
handleSearch = async (text: string) => {
const result = (await search({ text, filterRooms: false })) as ISearch[];
this.setState({
search: result
});
};
onSearchChangeText(text: string) {
this.handleSearch(text);
}
dismiss = () => {
const { navigation } = this.props;
return navigation.pop();
};
createChannel = () => {
logEvent(events.NEW_MSG_CREATE_CHANNEL);
const { navigation } = this.props;
navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') });
};
createTeam = () => {
logEvent(events.NEW_MSG_CREATE_TEAM);
const { navigation } = this.props;
navigation.navigate('SelectedUsersViewCreateChannel', {
nextAction: () => navigation.navigate('CreateChannelView', { isTeam: true })
});
};
createGroupChat = () => {
logEvent(events.NEW_MSG_CREATE_GROUP_CHAT);
const { dispatch, maxUsers, navigation } = this.props;
navigation.navigate('SelectedUsersViewCreateChannel', {
nextAction: () => dispatch(createChannelRequest({ group: true })),
buttonText: I18n.t('Create'),
maxUsers
});
};
goRoom = (item: TGoRoomItem) => {
logEvent(events.NEW_MSG_CHAT_WITH_USER);
const { isMasterDetail, navigation } = this.props;
if (isMasterDetail) {
navigation.pop();
}
goRoom({ item, isMasterDetail });
};
renderButton = ({ onPress, testID, title, icon, first }: IButton) => {
const { theme } = this.props;
return (
<Touch onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }} testID={testID}>
<View
style={[
first ? sharedStyles.separatorVertical : sharedStyles.separatorBottom,
styles.button,
{ borderColor: themes[theme].separatorColor }
]}
>
<CustomIcon name={icon} size={24} color={themes[theme].tintColor} style={styles.buttonIcon} />
<Text style={[styles.buttonText, { color: themes[theme].tintColor }]}>{title}</Text>
</View>
</Touch>
);
};
createDiscussion = () => {
logEvent(events.NEW_MSG_CREATE_DISCUSSION);
Navigation.navigate('CreateDiscussionView');
};
handleHasPermission = async () => {
const {
createTeamPermission,
createDirectMessagePermission,
createPublicChannelPermission,
createPrivateChannelPermission,
createDiscussionPermission
} = this.props;
const permissions = [
createPublicChannelPermission,
createPrivateChannelPermission,
createTeamPermission,
createDirectMessagePermission,
createDiscussionPermission
];
const permissionsToCreate = await hasPermission(permissions);
this.setState({ permissions: permissionsToCreate });
};
renderHeader = () => {
const { maxUsers, theme, serverVersion } = this.props;
const { permissions } = this.state;
return (
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<SearchBox onChangeText={(text: string) => this.onSearchChangeText(text)} testID='new-message-view-search' />
<View style={styles.buttonContainer}>
{permissions[0] || permissions[1]
? this.renderButton({
onPress: this.createChannel,
title: I18n.t('Create_Channel'),
icon: 'channel-public',
testID: 'new-message-view-create-channel',
first: true
})
: null}
{compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.13.0') && permissions[2]
? this.renderButton({
onPress: this.createTeam,
title: I18n.t('Create_Team'),
icon: 'teams',
testID: 'new-message-view-create-team'
})
: null}
{maxUsers > 2 && permissions[3]
? this.renderButton({
onPress: this.createGroupChat,
title: I18n.t('Create_Direct_Messages'),
icon: 'message',
testID: 'new-message-view-create-direct-message'
})
: null}
{permissions[4]
? this.renderButton({
onPress: this.createDiscussion,
title: I18n.t('Create_Discussion'),
icon: 'discussions',
testID: 'new-message-view-create-discussion'
})
: null}
</View>
</View>
);
};
renderItem = ({ item, index }: { item: ISearch | TSubscriptionModel; index: number }) => {
const { search, chats } = this.state;
const { theme } = this.props;
let style = { borderColor: themes[theme].separatorColor };
if (index === 0) {
style = { ...style, ...sharedStyles.separatorTop };
}
if (search.length > 0 && index === search.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
if (search.length === 0 && index === chats.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
const itemSearch = item as ISearch;
const itemModel = item as TSubscriptionModel;
return (
<UserItem
name={itemSearch.search ? itemSearch.name : itemModel.fname || ''}
username={itemSearch.search ? itemSearch.username : itemModel.name}
onPress={() => this.goRoom(itemModel)}
testID={`new-message-view-item-${item.name}`}
style={style}
theme={theme}
/>
);
};
renderList = () => {
const { search, chats } = this.state;
const { theme } = this.props;
return (
<FlatList
data={search.length > 0 ? search : chats}
extraData={this.state}
keyExtractor={item => item._id || item.rid}
ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={List.Separator}
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
keyboardShouldPersistTaps='always'
/>
);
};
render() {
return (
<SafeAreaView testID='new-message-view'>
<StatusBar />
{this.renderList()}
</SafeAreaView>
);
}
}
const mapStateToProps = (state: IApplicationState) => ({
serverVersion: state.server.version as string,
isMasterDetail: state.app.isMasterDetail,
maxUsers: (state.settings.DirectMesssage_maxUsers as number) || 1,
createTeamPermission: state.permissions['create-team'],
createDirectMessagePermission: state.permissions['create-d'],
createPublicChannelPermission: state.permissions['create-c'],
createPrivateChannelPermission: state.permissions['create-p'],
createDiscussionPermission: state.permissions['start-discussion']
});
export default connect(mapStateToProps)(withTheme(NewMessageView));

View File

@ -0,0 +1,32 @@
import React from 'react';
import * as List from '../../containers/List';
import { themes } from '../../lib/constants';
import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
import { useTheme } from '../../theme';
interface IButton {
onPress: () => void;
testID: string;
title: string;
icon: TIconsName;
}
const ButtonCreate = ({ onPress, testID, title, icon }: IButton) => {
const { theme } = useTheme();
return (
<>
<List.Item
onPress={onPress}
testID={testID}
left={() => <CustomIcon name={icon} size={24} color={themes[theme].bodyText} />}
right={() => <CustomIcon name={'chevron-right'} size={24} color={themes[theme].bodyText} />}
title={title}
/>
<List.Separator />
</>
);
};
export default ButtonCreate;

View File

@ -0,0 +1,107 @@
import { StackNavigationProp } from '@react-navigation/stack';
import React, { useCallback } from 'react';
import { StyleSheet, View } from 'react-native';
import { useDispatch } from 'react-redux';
import { useNavigation } from '@react-navigation/native';
import { createChannelRequest } from '../../actions/createChannel';
import { themes } from '../../lib/constants';
import SearchBox from '../../containers/SearchBox';
import I18n from '../../i18n';
import Navigation from '../../lib/navigation/appNavigation';
import { useTheme } from '../../theme';
import { events, logEvent } from '../../lib/methods/helpers/log';
import { NewMessageStackParamList } from '../../stacks/types';
import { compareServerVersion } from '../../lib/methods/helpers';
import { useAppSelector, usePermissions } from '../../lib/hooks';
import ButtonCreate from './ButtonCreate';
const styles = StyleSheet.create({
container: {
paddingTop: 16
},
buttonContainer: {
paddingBottom: 16
}
});
const HeaderNewMessage = ({ maxUsers, onChangeText }: { maxUsers: number; onChangeText: (text: string) => void }) => {
const navigation = useNavigation<StackNavigationProp<NewMessageStackParamList, 'NewMessageView'>>();
const dispatch = useDispatch();
const { theme } = useTheme();
const serverVersion = useAppSelector(state => state.server.version as string);
const [
createPublicChannelPermission,
createPrivateChannelPermission,
createTeamPermission,
createDirectMessagePermission,
createDiscussionPermission
] = usePermissions(['create-c', 'create-p', 'create-team', 'create-d', 'start-discussion']);
const createChannel = useCallback(() => {
logEvent(events.NEW_MSG_CREATE_CHANNEL);
navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') });
}, [navigation]);
const createTeam = useCallback(() => {
logEvent(events.NEW_MSG_CREATE_TEAM);
navigation.navigate('SelectedUsersViewCreateChannel', {
nextAction: () => navigation.navigate('CreateChannelView', { isTeam: true })
});
}, [navigation]);
const createGroupChat = useCallback(() => {
logEvent(events.NEW_MSG_CREATE_GROUP_CHAT);
navigation.navigate('SelectedUsersViewCreateChannel', {
nextAction: () => dispatch(createChannelRequest({ group: true })),
buttonText: I18n.t('Create'),
maxUsers
});
}, [dispatch, maxUsers, navigation]);
const createDiscussion = useCallback(() => {
logEvent(events.NEW_MSG_CREATE_DISCUSSION);
Navigation.navigate('CreateDiscussionView');
}, []);
return (
<>
<View style={[styles.container, { backgroundColor: themes[theme].auxiliaryBackground }]}>
<View style={styles.buttonContainer}>
{createPublicChannelPermission || createPrivateChannelPermission ? (
<ButtonCreate
onPress={createChannel}
title={'Channel'}
icon={'channel-public'}
testID={'new-message-view-create-channel'}
/>
) : null}
{compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.13.0') && createTeamPermission ? (
<ButtonCreate onPress={createTeam} title={'Team'} icon={'teams'} testID={'new-message-view-create-team'} />
) : null}
{maxUsers > 2 && createDirectMessagePermission ? (
<ButtonCreate
onPress={createGroupChat}
title={'Direct_message'}
icon={'message'}
testID={'new-message-view-create-direct-message'}
/>
) : null}
{createDiscussionPermission ? (
<ButtonCreate
onPress={createDiscussion}
title={'Discussion'}
icon={'discussions'}
testID={'new-message-view-create-discussion'}
/>
) : null}
</View>
</View>
<SearchBox onChangeText={(text: string) => onChangeText(text)} testID='new-message-view-search' />
</>
);
};
export default HeaderNewMessage;

View File

@ -0,0 +1,115 @@
import { Q } from '@nozbe/watermelondb';
import { StackNavigationProp } from '@react-navigation/stack';
import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react';
import { FlatList } from 'react-native';
import { shallowEqual } from 'react-redux';
import { useNavigation } from '@react-navigation/native';
import * as HeaderButton from '../../containers/HeaderButton';
import * as List from '../../containers/List';
import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar';
import { ISearch, TSubscriptionModel } from '../../definitions';
import I18n from '../../i18n';
import database from '../../lib/database';
import { useTheme } from '../../theme';
import { goRoom as goRoomMethod, TGoRoomItem } from '../../lib/methods/helpers/goRoom';
import log, { events, logEvent } from '../../lib/methods/helpers/log';
import { NewMessageStackParamList } from '../../stacks/types';
import { search as searchMethod } from '../../lib/methods';
import { useAppSelector } from '../../lib/hooks';
import UserItem from '../../containers/UserItem';
import HeaderNewMessage from './HeaderNewMessage';
const QUERY_SIZE = 50;
type TItem = ISearch | TSubscriptionModel;
const NewMessageView = () => {
const [chats, setChats] = useState<TSubscriptionModel[]>([]);
const [search, setSearch] = useState<TItem[]>([]);
const { colors } = useTheme();
const navigation = useNavigation<StackNavigationProp<NewMessageStackParamList, 'NewMessageView'>>();
const { isMasterDetail, maxUsers, useRealName } = useAppSelector(
state => ({
isMasterDetail: state.app.isMasterDetail,
maxUsers: (state.settings.DirectMesssage_maxUsers as number) || 1,
useRealName: state.settings.UI_Use_Real_Name as boolean
}),
shallowEqual
);
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='new-message-view-close' />,
title: I18n.t('Create_New')
});
}, [navigation]);
useEffect(() => {
const init = async () => {
try {
const db = database.active;
const c = await db
.get('subscriptions')
.query(Q.where('t', 'd'), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc))
.fetch();
setChats(c);
} catch (e) {
log(e);
}
};
init();
}, []);
const handleSearch = useCallback(async (text: string) => {
const result = (await searchMethod({ text, filterRooms: false })) as ISearch[];
setSearch(result);
}, []);
const goRoom = useCallback(
(item: TGoRoomItem) => {
logEvent(events.NEW_MSG_CHAT_WITH_USER);
if (isMasterDetail) {
navigation.pop();
}
goRoomMethod({ item, isMasterDetail });
},
[isMasterDetail, navigation]
);
return (
<SafeAreaView testID='new-message-view'>
<StatusBar />
<FlatList
data={search.length > 0 ? search : chats}
keyExtractor={item => item._id || item.rid}
ListHeaderComponent={<HeaderNewMessage maxUsers={maxUsers} onChangeText={handleSearch} />}
renderItem={({ item }) => {
const itemSearch = item as ISearch;
const itemModel = item as TSubscriptionModel;
return (
<UserItem
name={useRealName && itemSearch.fname ? itemSearch.fname : itemModel.name}
username={itemSearch.search ? itemSearch.username : itemModel.name}
onPress={() => goRoom(itemModel)}
testID={`new-message-view-item-${item.name}`}
/>
);
}}
ItemSeparatorComponent={List.Separator}
ListFooterComponent={List.Separator}
contentContainerStyle={{ backgroundColor: colors.backgroundColor }}
keyboardShouldPersistTaps='always'
/>
</SafeAreaView>
);
};
export default NewMessageView;

View File

@ -127,22 +127,22 @@ class RegisterView extends React.Component<IProps, any> {
const { dispatch, Accounts_EmailVerification, navigation, Accounts_ManuallyApproveNewUsers } = this.props;
try {
await Services.register({
const user = await Services.register({
name,
email,
pass: password,
username,
...customFields
username
});
if (Accounts_EmailVerification) {
await navigation.goBack();
showErrorAlert(I18n.t('Verify_email_desc'), I18n.t('Registration_Succeeded'));
} else if (Accounts_ManuallyApproveNewUsers) {
await navigation.goBack();
showErrorAlert(I18n.t('Wait_activation_warning'), I18n.t('Registration_Succeeded'));
} else {
dispatch(loginRequest({ user: email, password }));
if (user.success) {
if (Accounts_EmailVerification) {
await navigation.goBack();
showErrorAlert(I18n.t('Verify_email_desc'), I18n.t('Registration_Succeeded'));
} else if (Accounts_ManuallyApproveNewUsers) {
await navigation.goBack();
showErrorAlert(I18n.t('Wait_activation_warning'), I18n.t('Registration_Succeeded'));
} else {
dispatch(loginRequest({ user: email, password }, false, false, customFields));
}
}
} catch (e: any) {
if (e.data?.errorType === 'username-invalid') {

View File

@ -64,10 +64,6 @@ interface IRoomActionsViewProps extends IActionSheetProvider, IBaseScreen<ChatsS
encryptionEnabled: boolean;
fontScale: number;
serverVersion: string | null;
addUserToJoinedRoomPermission?: string[];
addUserToAnyCRoomPermission?: string[];
addUserToAnyPRoomPermission?: string[];
createInviteLinksPermission?: string[];
editRoomPermission?: string[];
toggleRoomE2EEncryptionPermission?: string[];
viewBroadcastMemberListPermission?: string[];
@ -94,13 +90,12 @@ interface IRoomActionsViewState {
joined: boolean;
canViewMembers: boolean;
canAutoTranslate: boolean;
canAddUser: boolean;
canInviteUser: boolean;
canEdit: boolean;
canToggleEncryption: boolean;
canCreateTeam: boolean;
canAddChannelToTeam: boolean;
canConvertTeam: boolean;
loading: boolean;
}
class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomActionsViewState> {
@ -146,13 +141,12 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
joined: !!room,
canViewMembers: false,
canAutoTranslate: false,
canAddUser: false,
canInviteUser: false,
canEdit: false,
canToggleEncryption: false,
canCreateTeam: false,
canAddChannelToTeam: false,
canConvertTeam: false
canConvertTeam: false,
loading: false
};
if (room && room.observe && room.rid) {
this.roomObservable = room.observe();
@ -206,8 +200,6 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
}
const canAutoTranslate = canAutoTranslateMethod();
const canAddUser = await this.canAddUser();
const canInviteUser = await this.canInviteUser();
const canEdit = await this.canEdit();
const canToggleEncryption = await this.canToggleEncryption();
const canViewMembers = await this.canViewMembers();
@ -217,8 +209,6 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
this.setState({
canAutoTranslate,
canAddUser,
canInviteUser,
canEdit,
canToggleEncryption,
canViewMembers,
@ -261,40 +251,6 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
}
};
canAddUser = async () => {
const { room, joined } = this.state;
const { addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission } = this.props;
const { rid, t } = room;
let canAddUser = false;
const userInRoom = joined;
const permissions = await hasPermission(
[addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission],
rid
);
if (userInRoom && permissions[0]) {
canAddUser = true;
}
if (t === 'c' && permissions[1]) {
canAddUser = true;
}
if (t === 'p' && permissions[2]) {
canAddUser = true;
}
return canAddUser;
};
canInviteUser = async () => {
const { room } = this.state;
const { createInviteLinksPermission } = this.props;
const { rid } = room;
const permissions = await hasPermission([createInviteLinksPermission], rid);
const canInviteUser = permissions[0];
return canInviteUser;
};
canEdit = async () => {
const { room } = this.state;
const { editRoomPermission } = this.props;
@ -558,6 +514,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
if (!room.teamId) {
return;
}
this.setState({ loading: true });
const result = await Services.teamListRoomsOfUser({ teamId: room.teamId, userId });
if (result.success) {
@ -577,6 +534,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
this.convertTeamToChannelConfirmation();
}
}
this.setState({ loading: false });
} catch (e) {
this.convertTeamToChannelConfirmation();
}
@ -619,6 +577,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
if (!room.teamId) {
return;
}
this.setState({ loading: true });
const result = await Services.teamListRoomsOfUser({ teamId: room.teamId, userId });
if (result.success) {
@ -644,6 +603,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
});
}
}
this.setState({ loading: false });
} catch (e) {
showConfirmationAlert({
message: I18n.t('You_are_leaving_the_team', { team: getRoomTitle(room) }),
@ -705,7 +665,8 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
const data = teamRooms.map(team => ({
rid: team.teamId as string,
t: team.t,
name: team.name
name: team.name,
teamMain: team.teamMain
}));
navigation.navigate('SelectListView', {
title: 'Move_to_Team',
@ -934,7 +895,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
};
renderLastSection = () => {
const { room, joined } = this.state;
const { room, joined, loading } = this.state;
const { theme } = this.props;
const { t, blocker } = room;
@ -968,6 +929,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
<List.Section>
<List.Separator />
<List.Item
disabled={loading}
title='Leave'
onPress={() =>
this.onPressTouchable({
@ -1032,7 +994,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
};
teamToChannelActions = (t: string, room: ISubscription) => {
const { canEdit, canConvertTeam } = this.state;
const { canEdit, canConvertTeam, loading } = this.state;
const canConvertTeamToChannel = canEdit && canConvertTeam && !!room?.teamMain;
return (
@ -1041,6 +1003,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
<>
<List.Item
title='Convert_to_Channel'
disabled={loading}
onPress={() =>
this.onPressTouchable({
event: this.convertTeamToChannel
@ -1135,7 +1098,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
};
render() {
const { room, membersCount, canViewMembers, canAddUser, canInviteUser, joined, canAutoTranslate } = this.state;
const { room, membersCount, canViewMembers, joined, canAutoTranslate } = this.state;
const { rid, t, prid } = room;
const isGroupChatHandler = isGroupChat(room);
@ -1154,7 +1117,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
<List.Item
title='Members'
subtitle={membersCount > 0 ? `${membersCount} ${I18n.t('members')}` : undefined}
onPress={() => this.onPressTouchable({ route: 'RoomMembersView', params: { rid, room } })}
onPress={() => this.onPressTouchable({ route: 'RoomMembersView', params: { rid, room, joined: this.joined } })}
testID='room-actions-members'
left={() => <List.Icon name='team' />}
showActionIndicator
@ -1164,45 +1127,6 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
</>
) : null}
{['c', 'p'].includes(t) && canAddUser ? (
<>
<List.Item
title='Add_users'
onPress={() =>
this.onPressTouchable({
route: 'SelectedUsersView',
params: {
title: I18n.t('Add_users'),
nextAction: this.addUser
}
})
}
testID='room-actions-add-user'
left={() => <List.Icon name='add' />}
showActionIndicator
/>
<List.Separator />
</>
) : null}
{['c', 'p'].includes(t) && canInviteUser ? (
<>
<List.Item
title='Invite_users'
onPress={() =>
this.onPressTouchable({
route: 'InviteUsersView',
params: { rid }
})
}
testID='room-actions-invite-user'
left={() => <List.Icon name='user-add' />}
showActionIndicator
/>
<List.Separator />
</>
) : null}
{['c', 'p', 'd'].includes(t) && !prid ? (
<>
<List.Item
@ -1384,10 +1308,6 @@ const mapStateToProps = (state: IApplicationState) => ({
encryptionEnabled: state.encryption.enabled,
serverVersion: state.server.version,
isMasterDetail: state.app.isMasterDetail,
addUserToJoinedRoomPermission: state.permissions['add-user-to-joined-room'],
addUserToAnyCRoomPermission: state.permissions['add-user-to-any-c-room'],
addUserToAnyPRoomPermission: state.permissions['add-user-to-any-p-room'],
createInviteLinksPermission: state.permissions['create-invite-links'],
editRoomPermission: state.permissions['edit-room'],
toggleRoomE2EEncryptionPermission: state.permissions['toggle-room-e2e-encryption'],
viewBroadcastMemberListPermission: state.permissions['view-broadcast-member-list'],

View File

@ -705,7 +705,7 @@ class RoomInfoEditView extends React.Component<IRoomInfoEditViewProps, IRoomInfo
) : null}
{room.broadcast
? [
<Text style={styles.broadcast}>{I18n.t('Broadcast_Channel')}</Text>,
<Text style={styles.broadcast}>{I18n.t('Broadcast')}</Text>,
<View style={[styles.divider, { borderColor: themes[theme].separatorColor }]} />
]
: null}

View File

@ -24,8 +24,8 @@ const Channel = ({ room }: { room: ISubscription }) => {
testID='room-info-view-announcement'
/>
<Item
label={I18n.t('Broadcast_Channel')}
content={room.broadcast ? I18n.t('Broadcast_channel_Description') : ''}
label={I18n.t('Broadcast')}
content={room.broadcast ? I18n.t('Broadcast_hint') : ''}
testID='room-info-view-broadcast'
/>
</>

View File

@ -0,0 +1,109 @@
import { CompositeNavigationProp, useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import React from 'react';
import { View } from 'react-native';
import { useDispatch } from 'react-redux';
import { setLoading } from '../../../actions/selectedUsers';
import * as List from '../../../containers/List';
import { TSubscriptionModel } from '../../../definitions';
import i18n from '../../../i18n';
import { usePermissions } from '../../../lib/hooks';
import log, { events, logEvent } from '../../../lib/methods/helpers/log';
import { Services } from '../../../lib/services';
import { MasterDetailInsideStackParamList } from '../../../stacks/MasterDetailStack/types';
import { ChatsStackParamList } from '../../../stacks/types';
type TNavigation = CompositeNavigationProp<
StackNavigationProp<ChatsStackParamList, 'RoomActionsView'>,
StackNavigationProp<MasterDetailInsideStackParamList>
>;
interface IActionsSection {
rid: TSubscriptionModel['rid'];
t: TSubscriptionModel['t'];
joined: boolean;
}
export default function ActionsSection({ rid, t, joined }: IActionsSection): React.ReactElement {
const { navigate, pop } = useNavigation<TNavigation>();
const dispatch = useDispatch();
const [addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission, createInviteLinksPermission] =
usePermissions(['add-user-to-joined-room', 'add-user-to-any-c-room', 'add-user-to-any-p-room', 'create-invite-links'], rid);
const canAddUser =
(joined && addUserToJoinedRoomPermission) ||
(t === 'c' && addUserToAnyCRoomPermission) ||
(t === 'p' && addUserToAnyPRoomPermission) ||
false;
const canInviteUser = createInviteLinksPermission;
const handleOnPress = ({
route,
params
}: {
route: keyof ChatsStackParamList;
params: ChatsStackParamList[keyof ChatsStackParamList];
}) => {
navigate(route, params);
// @ts-ignore
logEvent(events[`RM_GO_${route.replace('View', '').toUpperCase()}`]);
};
const addUser = async () => {
try {
dispatch(setLoading(true));
await Services.addUsersToRoom(rid);
pop();
} catch (e) {
log(e);
} finally {
dispatch(setLoading(false));
}
};
return (
<View style={{ paddingTop: canAddUser || canInviteUser ? 16 : 0, paddingBottom: canAddUser || canInviteUser ? 8 : 0 }}>
{['c', 'p'].includes(t) && canAddUser ? (
<>
<List.Separator />
<List.Item
title='Add_users'
onPress={() =>
handleOnPress({
route: 'SelectedUsersView',
params: {
title: i18n.t('Add_users'),
nextAction: addUser
}
})
}
testID='room-actions-add-user'
left={() => <List.Icon name='add' />}
showActionIndicator
/>
<List.Separator />
</>
) : null}
{['c', 'p'].includes(t) && canInviteUser ? (
<>
<List.Item
title='Invite_users'
onPress={() =>
handleOnPress({
route: 'InviteUsersView',
params: { rid }
})
}
testID='room-actions-invite-user'
left={() => <List.Icon name='user-add' />}
showActionIndicator
/>
<List.Separator />
</>
) : null}
</View>
);
}

View File

@ -0,0 +1,259 @@
import { Q } from '@nozbe/watermelondb';
import { LISTENER } from '../../containers/Toast';
import { IUser, SubscriptionType, TSubscriptionModel, TUserModel } from '../../definitions';
import I18n from '../../i18n';
import { getRoomTitle, showConfirmationAlert, showErrorAlert } from '../../lib/methods/helpers';
import EventEmitter from '../../lib/methods/helpers/events';
import { goRoom, TGoRoomItem } from '../../lib/methods/helpers/goRoom';
import log from '../../lib/methods/helpers/log';
import appNavigation from '../../lib/navigation/appNavigation';
import { Services } from '../../lib/services';
import database from '../../lib/database';
import { RoomTypes } from '../../lib/methods';
export type TRoomType = SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL;
const handleGoRoom = (item: TGoRoomItem, isMasterDetail: boolean): void => {
if (isMasterDetail) {
appNavigation.navigate('DrawerNavigator');
} else {
appNavigation.popToTop();
}
goRoom({ item, isMasterDetail });
};
export const fetchRole = (role: string, selectedUser: TUserModel, roomRoles: any): boolean => {
const userRoleResult = roomRoles.find((r: any) => r.u._id === selectedUser._id);
return userRoleResult?.roles.includes(role);
};
export const fetchRoomMembersRoles = async (roomType: TRoomType, rid: string, updateState: any): Promise<void> => {
try {
const type = roomType;
const result = await Services.getRoomRoles(rid, type);
if (result?.success) {
updateState({ roomRoles: result.roles });
}
} catch (e) {
log(e);
}
};
export const handleMute = async (user: TUserModel, rid: string) => {
try {
await Services.toggleMuteUserInRoom(rid, user?.username, !user?.muted);
EventEmitter.emit(LISTENER, {
message: I18n.t('User_has_been_key', { key: user?.muted ? I18n.t('unmuted') : I18n.t('muted') })
});
} catch (e) {
log(e);
}
};
export const handleModerator = async (
selectedUser: TUserModel,
isModerator: boolean,
room: TSubscriptionModel,
username: string,
callback: () => Promise<void>
): Promise<void> => {
try {
await Services.toggleRoomModerator({
roomId: room.rid,
t: room.t,
userId: selectedUser._id,
isModerator
});
const message = isModerator
? 'User__username__is_now_a_moderator_of__room_name_'
: 'User__username__removed_from__room_name__moderators';
EventEmitter.emit(LISTENER, {
message: I18n.t(message, {
username,
room_name: getRoomTitle(room)
})
});
callback();
} catch (e) {
log(e);
}
};
export const navToDirectMessage = async (item: IUser, isMasterDetail: boolean): Promise<void> => {
try {
const db = database.active;
const subsCollection = db.get('subscriptions');
const query = await subsCollection.query(Q.where('name', item.username)).fetch();
if (query.length) {
const [room] = query;
handleGoRoom(room, isMasterDetail);
} else {
const result = await Services.createDirectMessage(item.username);
if (result.success) {
handleGoRoom({ rid: result.room?._id as string, name: item.username, t: SubscriptionType.DIRECT }, isMasterDetail);
}
}
} catch (e) {
log(e);
}
};
const removeFromTeam = async (
selectedUser: IUser,
updateState: Function,
room: TSubscriptionModel,
members: TUserModel[],
selected?: any
) => {
try {
const userId = selectedUser._id;
const result = await Services.removeTeamMember({
teamId: room.teamId,
userId,
...(selected && { rooms: selected })
});
if (result.success) {
const message = I18n.t('User_has_been_removed_from_s', { s: getRoomTitle(room) });
EventEmitter.emit(LISTENER, { message });
const newMembers = members.filter(member => member._id !== userId);
updateState({
members: newMembers
});
appNavigation.navigate('RoomMembersView', { room });
}
} catch (e: any) {
log(e);
showErrorAlert(
e.data.error ? I18n.t(e.data.error) : I18n.t('There_was_an_error_while_action', { action: I18n.t('removing_team') }),
I18n.t('Cannot_remove')
);
}
};
export const handleRemoveFromTeam = async (
selectedUser: TUserModel,
updateState: Function,
room: TSubscriptionModel,
members: TUserModel[]
): Promise<void> => {
try {
const result = await Services.teamListRoomsOfUser({ teamId: room.teamId as string, userId: selectedUser._id });
if (result.success) {
if (result.rooms?.length) {
const teamChannels = result.rooms.map((r: any) => ({
rid: r._id,
name: r.name,
teamId: r.teamId,
alert: r.isLastOwner
}));
appNavigation.navigate('SelectListView', {
title: 'Remove_Member',
infoText: 'Remove_User_Team_Channels',
data: teamChannels,
nextAction: (selected: any) => removeFromTeam(selectedUser, updateState, room, members, selected),
showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_remove'))
});
} else {
showConfirmationAlert({
message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }),
onPress: () => removeFromTeam(selectedUser, updateState, room, members)
});
}
}
} catch (e) {
showConfirmationAlert({
message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }),
onPress: () => removeFromTeam(selectedUser, updateState, room, members)
});
}
};
export const handleLeader = async (
selectedUser: TUserModel,
isLeader: boolean,
room: TSubscriptionModel,
username: string,
callback: () => Promise<void>
): Promise<void> => {
try {
await Services.toggleRoomLeader({
roomId: room.rid,
t: room.t,
userId: selectedUser._id,
isLeader
});
const message = isLeader
? 'User__username__is_now_a_leader_of__room_name_'
: 'User__username__removed_from__room_name__leaders';
EventEmitter.emit(LISTENER, {
message: I18n.t(message, {
username,
room_name: getRoomTitle(room)
})
});
callback();
} catch (e) {
log(e);
}
};
export const handleRemoveUserFromRoom = async (
selectedUser: TUserModel,
room: TSubscriptionModel,
callback: Function
): Promise<void> => {
try {
const userId = selectedUser._id;
await Services.removeUserFromRoom({ roomId: room.rid, t: room.t as RoomTypes, userId });
const message = I18n.t('User_has_been_removed_from_s', { s: getRoomTitle(room) });
EventEmitter.emit(LISTENER, { message });
callback();
} catch (e) {
log(e);
}
};
export const handleIgnore = async (selectedUser: TUserModel, ignore: boolean, rid: string) => {
try {
await Services.ignoreUser({
rid,
userId: selectedUser._id,
ignore
});
const message = I18n.t(ignore ? 'User_has_been_ignored' : 'User_has_been_unignored');
EventEmitter.emit(LISTENER, { message });
} catch (e) {
log(e);
}
};
export const handleOwner = async (
selectedUser: TUserModel,
isOwner: boolean,
username: string,
room: TSubscriptionModel,
callback: Function
): Promise<void> => {
try {
await Services.toggleRoomOwner({
roomId: room.rid,
t: room.t,
userId: selectedUser._id,
isOwner
});
const message = isOwner ? 'User__username__is_now_a_owner_of__room_name_' : 'User__username__removed_from__room_name__owners';
EventEmitter.emit(LISTENER, {
message: I18n.t(message, {
username,
room_name: getRoomTitle(room)
})
});
} catch (e) {
log(e);
}
callback();
};

View File

@ -1,311 +1,202 @@
import { Q } from '@nozbe/watermelondb';
import React from 'react';
import { FlatList } from 'react-native';
import { connect } from 'react-redux';
import { Observable, Subscription } from 'rxjs';
import { NavigationProp, RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import React, { useEffect, useReducer } from 'react';
import { FlatList, Text, View } from 'react-native';
import { themes } from '../../lib/constants';
import { TActionSheetOptions, TActionSheetOptionsItem, withActionSheet } from '../../containers/ActionSheet';
import { TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet';
import ActivityIndicator from '../../containers/ActivityIndicator';
import { CustomIcon } from '../../containers/CustomIcon';
import * as HeaderButton from '../../containers/HeaderButton';
import * as List from '../../containers/List';
import { RadioButton } from '../../containers/RadioButton';
import SafeAreaView from '../../containers/SafeAreaView';
import SearchBox from '../../containers/SearchBox';
import StatusBar from '../../containers/StatusBar';
import { LISTENER } from '../../containers/Toast';
import { IApplicationState, IBaseScreen, IUser, SubscriptionType, TSubscriptionModel, TUserModel } from '../../definitions';
import I18n from '../../i18n';
import database from '../../lib/database';
import { CustomIcon } from '../../containers/CustomIcon';
import UserItem from '../../containers/UserItem';
import { getUserSelector } from '../../selectors/login';
import { ModalStackParamList } from '../../stacks/MasterDetailStack/types';
import { TSupportedThemes, withTheme } from '../../theme';
import EventEmitter from '../../lib/methods/helpers/events';
import { goRoom, TGoRoomItem } from '../../lib/methods/helpers/goRoom';
import { showConfirmationAlert, showErrorAlert } from '../../lib/methods/helpers/info';
import { TSubscriptionModel, TUserModel } from '../../definitions';
import I18n from '../../i18n';
import { useAppSelector, usePermissions } from '../../lib/hooks';
import { getRoomTitle, isGroupChat } from '../../lib/methods/helpers';
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
import log from '../../lib/methods/helpers/log';
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
import { TSupportedPermissions } from '../../reducers/permissions';
import { RoomTypes } from '../../lib/methods';
import { compareServerVersion, debounce, getRoomTitle, hasPermission, isGroupChat } from '../../lib/methods/helpers';
import styles from './styles';
import { Services } from '../../lib/services';
import { TSupportedPermissions } from '../../reducers/permissions';
import { getUserSelector } from '../../selectors/login';
import { ModalStackParamList } from '../../stacks/MasterDetailStack/types';
import { useTheme } from '../../theme';
import ActionsSection from './components/ActionsSection';
import {
fetchRole,
fetchRoomMembersRoles,
handleIgnore,
handleLeader,
handleModerator,
handleMute,
handleOwner,
handleRemoveFromTeam,
handleRemoveUserFromRoom,
navToDirectMessage,
TRoomType
} from './helpers';
import styles from './styles';
const PAGE_SIZE = 25;
interface IRoomMembersViewProps extends IBaseScreen<ModalStackParamList, 'RoomMembersView'> {
rid: string;
members: string[];
baseUrl: string;
room: TSubscriptionModel;
user: {
id: string;
token: string;
roles: string[];
};
showActionSheet: (params: TActionSheetOptions) => {};
theme: TSupportedThemes;
isMasterDetail: boolean;
useRealName: boolean;
muteUserPermission: string[];
setLeaderPermission: string[];
setOwnerPermission: string[];
setModeratorPermission: string[];
removeUserPermission: string[];
editTeamMemberPermission: string[];
viewAllTeamChannelsPermission: string[];
viewAllTeamsPermission: string[];
serverVersion: string;
}
interface IRoomMembersViewState {
isLoading: boolean;
allUsers: boolean;
filtering: string;
rid: string;
members: TUserModel[];
membersFiltered: TUserModel[];
room: TSubscriptionModel;
end: boolean;
roomRoles: any;
filter: string;
page: number;
}
class RoomMembersView extends React.Component<IRoomMembersViewProps, IRoomMembersViewState> {
private mounted: boolean;
private permissions: { [key in TSupportedPermissions]?: boolean };
private roomObservable!: Observable<TSubscriptionModel>;
private subscription!: Subscription;
private roomRoles: any;
const RightIcon = ({ check, label }: { check: boolean; label: string }) => {
const { colors } = useTheme();
return (
<CustomIcon
testID={check ? `action-sheet-set-${label}-checked` : `action-sheet-set-${label}-unchecked`}
name={check ? 'checkbox-checked' : 'checkbox-unchecked'}
size={20}
color={check ? colors.tintActive : colors.auxiliaryTintColor}
/>
);
};
constructor(props: IRoomMembersViewProps) {
super(props);
this.mounted = false;
this.permissions = {};
const rid = props.route.params?.rid;
const room = props.route.params?.room;
this.state = {
const RoomMembersView = (): React.ReactElement => {
const { showActionSheet } = useActionSheet();
const { colors } = useTheme();
const { params } = useRoute<RouteProp<ModalStackParamList, 'RoomMembersView'>>();
const navigation = useNavigation<NavigationProp<ModalStackParamList, 'RoomMembersView'>>();
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name);
const user = useAppSelector(state => getUserSelector(state));
const [state, updateState] = useReducer(
(state: IRoomMembersViewState, newState: Partial<IRoomMembersViewState>) => ({ ...state, ...newState }),
{
isLoading: false,
allUsers: false,
filtering: '',
rid,
members: [],
membersFiltered: [],
room: room || ({} as TSubscriptionModel),
room: params.room || ({} as TSubscriptionModel),
end: false,
roomRoles: null,
filter: '',
page: 0
}
);
const teamPermissions: TSupportedPermissions[] = state.room.teamMain
? ['edit-team-member', 'view-all-team-channels', 'view-all-teams']
: [];
const [
muteUserPermission,
setLeaderPermission,
setOwnerPermission,
setModeratorPermission,
removeUserPermission,
editTeamMemberPermission,
viewAllTeamChannelsPermission,
viewAllTeamsPermission
] = usePermissions(['mute-user', 'set-leader', 'set-owner', 'set-moderator', 'remove-user', ...teamPermissions], params.rid);
useEffect(() => {
const subscription = params?.room?.observe && params.room.observe().subscribe(changes => updateState({ room: changes }));
setHeader(true);
fetchMembers(true);
return () => subscription?.unsubscribe();
}, []);
useEffect(() => {
const fetchRoles = () => {
if (isGroupChat(state.room)) {
return;
}
if (
muteUserPermission ||
setLeaderPermission ||
setOwnerPermission ||
setModeratorPermission ||
removeUserPermission ||
editTeamMemberPermission ||
viewAllTeamChannelsPermission ||
viewAllTeamsPermission
) {
fetchRoomMembersRoles(state.room.t as any, state.room.rid, updateState);
}
};
if (room && room.observe) {
this.roomObservable = room.observe();
this.subscription = this.roomObservable.subscribe(changes => {
if (this.mounted) {
this.setState({ room: changes });
} else {
this.setState({ room: changes });
}
});
fetchRoles();
}, [
muteUserPermission,
setLeaderPermission,
setOwnerPermission,
setModeratorPermission,
removeUserPermission,
editTeamMemberPermission,
viewAllTeamChannelsPermission,
viewAllTeamsPermission
]);
const toggleStatus = (status: boolean) => {
try {
updateState({ members: [], allUsers: status, end: false });
fetchMembers(status);
setHeader(status);
} catch (e) {
log(e);
}
this.setHeader();
}
};
async componentDidMount() {
const { room } = this.state;
this.mounted = true;
this.fetchMembers();
if (isGroupChat(room)) {
return;
}
const {
muteUserPermission,
setLeaderPermission,
setOwnerPermission,
setModeratorPermission,
removeUserPermission,
editTeamMemberPermission,
viewAllTeamChannelsPermission,
viewAllTeamsPermission
} = this.props;
const result = await hasPermission(
[
muteUserPermission,
setLeaderPermission,
setOwnerPermission,
setModeratorPermission,
removeUserPermission,
...(room.teamMain ? [editTeamMemberPermission, viewAllTeamChannelsPermission, viewAllTeamsPermission] : [])
],
room.rid
);
this.permissions = {
'mute-user': result[0],
'set-leader': result[1],
'set-owner': result[2],
'set-moderator': result[3],
'remove-user': result[4],
...(room.teamMain
? {
'edit-team-member': result[5],
'view-all-team-channels': result[6],
'view-all-teams': result[7]
}
: {})
};
const hasSinglePermission = Object.values(this.permissions).some(p => !!p);
if (hasSinglePermission) {
this.fetchRoomMembersRoles();
}
}
componentWillUnmount() {
if (this.subscription && this.subscription.unsubscribe) {
this.subscription.unsubscribe();
}
}
setHeader = () => {
const { allUsers } = this.state;
const { navigation } = this.props;
const toggleText = allUsers ? I18n.t('Online') : I18n.t('All');
const setHeader = (allUsers: boolean) => {
navigation.setOptions({
title: I18n.t('Members'),
headerRight: () => (
<HeaderButton.Container>
<HeaderButton.Item title={toggleText} onPress={this.toggleStatus} testID='room-members-view-toggle-status' />
<HeaderButton.Item
iconName='filter'
onPress={() =>
showActionSheet({
options: [
{
title: I18n.t('Online'),
onPress: () => toggleStatus(true),
right: () => <RadioButton check={allUsers} />,
testID: 'room-members-view-toggle-status-online'
},
{
title: I18n.t('All'),
onPress: () => toggleStatus(false),
right: () => <RadioButton check={!allUsers} />,
testID: 'room-members-view-toggle-status-all'
}
]
})
}
testID='room-members-view-filter'
/>
</HeaderButton.Container>
)
});
};
get isServerVersionLowerThan3_16() {
const { serverVersion } = this.props;
return compareServerVersion(serverVersion, 'lowerThan', '3.16.0');
}
const getUserDisplayName = (user: TUserModel) => (useRealName ? user.name : user.username) || user.username;
onSearchChangeText = debounce((text: string) => {
const { members } = this.state;
text = text.trim();
if (this.isServerVersionLowerThan3_16) {
let membersFiltered: TUserModel[] = [];
if (members && members.length > 0 && text) {
membersFiltered = members.filter(
m => m.username.toLowerCase().match(text.toLowerCase()) || m.name?.toLowerCase().match(text.toLowerCase())
);
}
return this.setState({ filtering: text, membersFiltered });
}
this.setState({ filtering: text, page: 0, members: [], end: false }, () => {
this.fetchMembers();
});
}, 500);
navToDirectMessage = async (item: IUser) => {
try {
const db = database.active;
const subsCollection = db.get('subscriptions');
const query = await subsCollection.query(Q.where('name', item.username)).fetch();
if (query.length) {
const [room] = query;
this.goRoom(room);
} else {
const result = await Services.createDirectMessage(item.username);
if (result.success) {
this.goRoom({ rid: result.room?._id as string, name: item.username, t: SubscriptionType.DIRECT });
}
}
} catch (e) {
log(e);
}
};
handleRemoveFromTeam = async (selectedUser: TUserModel) => {
try {
const { navigation } = this.props;
const { room } = this.state;
const result = await Services.teamListRoomsOfUser({ teamId: room.teamId as string, userId: selectedUser._id });
if (result.success) {
if (result.rooms?.length) {
const teamChannels = result.rooms.map((r: any) => ({
rid: r._id,
name: r.name,
teamId: r.teamId,
alert: r.isLastOwner
}));
navigation.navigate('SelectListView', {
title: 'Remove_Member',
infoText: 'Remove_User_Team_Channels',
data: teamChannels,
nextAction: (selected: any) => this.removeFromTeam(selectedUser, selected),
showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_remove'))
});
} else {
showConfirmationAlert({
message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }),
onPress: () => this.removeFromTeam(selectedUser)
});
}
}
} catch (e) {
showConfirmationAlert({
message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }),
onPress: () => this.removeFromTeam(selectedUser)
});
}
};
removeFromTeam = async (selectedUser: IUser, selected?: any) => {
try {
const { members, membersFiltered, room } = this.state;
const { navigation } = this.props;
const userId = selectedUser._id;
const result = await Services.removeTeamMember({
teamId: room.teamId,
userId,
...(selected && { rooms: selected })
});
if (result.success) {
const message = I18n.t('User_has_been_removed_from_s', { s: getRoomTitle(room) });
EventEmitter.emit(LISTENER, { message });
const newMembers = members.filter(member => member._id !== userId);
const newMembersFiltered = this.isServerVersionLowerThan3_16
? membersFiltered.filter(member => member._id !== userId)
: [];
this.setState({
members: newMembers,
membersFiltered: newMembersFiltered
});
// @ts-ignore - This is just to force a reload
navigation.navigate('RoomMembersView');
}
} catch (e: any) {
log(e);
showErrorAlert(
e.data.error ? I18n.t(e.data.error) : I18n.t('There_was_an_error_while_action', { action: I18n.t('removing_team') }),
I18n.t('Cannot_remove')
);
}
};
onPressUser = (selectedUser: TUserModel) => {
const { room } = this.state;
const { showActionSheet, user, theme } = this.props;
const onPressUser = (selectedUser: TUserModel) => {
const { room, roomRoles, members } = state;
const options: TActionSheetOptionsItem[] = [
{
icon: 'message',
title: I18n.t('Direct_message'),
onPress: () => this.navToDirectMessage(selectedUser)
onPress: () => navToDirectMessage(selectedUser, isMasterDetail)
}
];
@ -316,12 +207,12 @@ class RoomMembersView extends React.Component<IRoomMembersViewProps, IRoomMember
options.push({
icon: 'ignore',
title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'),
onPress: () => this.handleIgnore(selectedUser, !isIgnored),
onPress: () => handleIgnore(selectedUser, !isIgnored, room.rid),
testID: 'action-sheet-ignore-user'
});
}
if (this.permissions['mute-user']) {
if (muteUserPermission) {
const { muted = [] } = room;
const userIsMuted = muted.find?.(m => m === selectedUser.username);
selectedUser.muted = !!userIsMuted;
@ -334,7 +225,7 @@ class RoomMembersView extends React.Component<IRoomMembersViewProps, IRoomMember
roomName: getRoomTitle(room)
}),
confirmationText: I18n.t(userIsMuted ? 'Unmute' : 'Mute'),
onPress: () => this.handleMute(selectedUser)
onPress: () => handleMute(selectedUser, room.rid)
});
},
testID: 'action-sheet-mute-user'
@ -342,78 +233,63 @@ class RoomMembersView extends React.Component<IRoomMembersViewProps, IRoomMember
}
// Owner
if (this.permissions['set-owner']) {
const userRoleResult = this.roomRoles.find((r: any) => r.u._id === selectedUser._id);
const isOwner = userRoleResult?.roles.includes('owner');
if (setOwnerPermission) {
const isOwner = fetchRole('owner', selectedUser, roomRoles);
options.push({
icon: 'shield-check',
title: I18n.t('Owner'),
onPress: () => this.handleOwner(selectedUser, !isOwner),
right: () => (
<CustomIcon
testID={isOwner ? 'action-sheet-set-owner-checked' : 'action-sheet-set-owner-unchecked'}
name={isOwner ? 'checkbox-checked' : 'checkbox-unchecked'}
size={20}
color={isOwner ? themes[theme].tintActive : themes[theme].auxiliaryTintColor}
/>
),
onPress: () =>
handleOwner(selectedUser, !isOwner, getUserDisplayName(selectedUser), room, () =>
fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState)
),
right: () => <RightIcon check={isOwner} label='owner' />,
testID: 'action-sheet-set-owner'
});
}
// Leader
if (this.permissions['set-leader']) {
const userRoleResult = this.roomRoles.find((r: any) => r.u._id === selectedUser._id);
const isLeader = userRoleResult?.roles.includes('leader');
if (setLeaderPermission) {
const isLeader = fetchRole('leader', selectedUser, roomRoles);
options.push({
icon: 'shield-alt',
title: I18n.t('Leader'),
onPress: () => this.handleLeader(selectedUser, !isLeader),
right: () => (
<CustomIcon
testID={isLeader ? 'action-sheet-set-leader-checked' : 'action-sheet-set-leader-unchecked'}
name={isLeader ? 'checkbox-checked' : 'checkbox-unchecked'}
size={20}
color={isLeader ? themes[theme].tintActive : themes[theme].auxiliaryTintColor}
/>
),
onPress: () =>
handleLeader(selectedUser, !isLeader, room, getUserDisplayName(selectedUser), () =>
fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState)
),
right: () => <RightIcon check={isLeader} label='leader' />,
testID: 'action-sheet-set-leader'
});
}
// Moderator
if (this.permissions['set-moderator']) {
const userRoleResult = this.roomRoles.find((r: any) => r.u._id === selectedUser._id);
const isModerator = userRoleResult?.roles.includes('moderator');
if (setModeratorPermission) {
const isModerator = fetchRole('moderator', selectedUser, roomRoles);
options.push({
icon: 'shield',
title: I18n.t('Moderator'),
onPress: () => this.handleModerator(selectedUser, !isModerator),
right: () => (
<CustomIcon
testID={isModerator ? 'action-sheet-set-moderator-checked' : 'action-sheet-set-moderator-unchecked'}
name={isModerator ? 'checkbox-checked' : 'checkbox-unchecked'}
size={20}
color={isModerator ? themes[theme].tintActive : themes[theme].auxiliaryTintColor}
/>
),
onPress: () =>
handleModerator(selectedUser, !isModerator, room, getUserDisplayName(selectedUser), () =>
fetchRoomMembersRoles(room.t as TRoomType, room.rid, updateState)
),
right: () => <RightIcon check={isModerator} label='moderator' />,
testID: 'action-sheet-set-moderator'
});
}
// Remove from team
if (this.permissions['edit-team-member']) {
if (editTeamMemberPermission) {
options.push({
icon: 'logout',
danger: true,
title: I18n.t('Remove_from_Team'),
onPress: () => this.handleRemoveFromTeam(selectedUser),
onPress: () => handleRemoveFromTeam(selectedUser, updateState, room, members),
testID: 'action-sheet-remove-from-team'
});
}
// Remove from room
if (this.permissions['remove-user'] && !room.teamMain) {
if (removeUserPermission && !room.teamMain) {
options.push({
icon: 'logout',
title: I18n.t('Remove_from_room'),
@ -422,7 +298,13 @@ class RoomMembersView extends React.Component<IRoomMembersViewProps, IRoomMember
showConfirmationAlert({
message: I18n.t('The_user_will_be_removed_from_s', { s: getRoomTitle(room) }),
confirmationText: I18n.t('Yes_remove_user'),
onPress: () => this.handleRemoveUserFromRoom(selectedUser)
onPress: () => {
handleRemoveUserFromRoom(selectedUser, room, () =>
updateState({
members: members.filter(member => member._id !== selectedUser._id)
})
);
}
});
},
testID: 'action-sheet-remove-from-room'
@ -435,256 +317,83 @@ class RoomMembersView extends React.Component<IRoomMembersViewProps, IRoomMember
});
};
toggleStatus = () => {
try {
const { allUsers } = this.state;
this.setState({ members: [], allUsers: !allUsers, end: false, page: 0 }, () => {
this.fetchMembers();
});
} catch (e) {
log(e);
}
};
fetchRoomMembersRoles = async () => {
try {
const { room } = this.state;
const type = room.t as SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL;
const result = await Services.getRoomRoles(room.rid, type);
if (result?.success) {
this.roomRoles = result.roles;
}
} catch (e) {
log(e);
}
};
fetchMembers = async () => {
const { rid, members, isLoading, allUsers, end, room, filtering, page } = this.state;
const fetchMembers = async (status: boolean) => {
const { members, isLoading, end, room, filter, page } = state;
const { t } = room;
if (isLoading || end) {
return;
}
this.setState({ isLoading: true });
updateState({ isLoading: true });
try {
const membersResult = await Services.getRoomMembers({
rid,
rid: room.rid,
roomType: t,
type: allUsers ? 'all' : 'online',
filter: filtering,
type: !status ? 'all' : 'online',
filter,
skip: PAGE_SIZE * page,
limit: PAGE_SIZE,
allUsers
allUsers: !status
});
const end = membersResult?.length < PAGE_SIZE;
const membersResultFiltered = membersResult?.filter((member: TUserModel) => !members.some(m => m._id === member._id));
this.setState({
members: members.concat(membersResultFiltered || []),
updateState({
members: [...members, ...membersResultFiltered],
isLoading: false,
end,
page: page + 1
});
this.setHeader();
} catch (e) {
log(e);
this.setState({ isLoading: false });
updateState({ isLoading: false });
}
};
goRoom = (item: TGoRoomItem) => {
const { navigation, isMasterDetail } = this.props;
if (isMasterDetail) {
// @ts-ignore
navigation.navigate('DrawerNavigator');
} else {
navigation.popToTop();
}
goRoom({ item, isMasterDetail });
};
const filteredMembers =
state.members && state.members.length > 0 && state.filter
? state.members.filter(
m =>
m.username.toLowerCase().match(state.filter.toLowerCase()) || m.name?.toLowerCase().match(state.filter.toLowerCase())
)
: null;
getUserDisplayName = (user: TUserModel) => {
const { useRealName } = this.props;
return (useRealName ? user.name : user.username) || user.username;
};
handleMute = async (user: TUserModel) => {
const { rid } = this.state;
try {
await Services.toggleMuteUserInRoom(rid, user?.username, !user?.muted);
EventEmitter.emit(LISTENER, {
message: I18n.t('User_has_been_key', { key: user?.muted ? I18n.t('unmuted') : I18n.t('muted') })
});
} catch (e) {
log(e);
}
};
handleOwner = async (selectedUser: TUserModel, isOwner: boolean) => {
try {
const { room } = this.state;
await Services.toggleRoomOwner({
roomId: room.rid,
t: room.t,
userId: selectedUser._id,
isOwner
});
const message = isOwner
? 'User__username__is_now_a_owner_of__room_name_'
: 'User__username__removed_from__room_name__owners';
EventEmitter.emit(LISTENER, {
message: I18n.t(message, {
username: this.getUserDisplayName(selectedUser),
room_name: getRoomTitle(room)
})
});
} catch (e) {
log(e);
}
this.fetchRoomMembersRoles();
};
handleLeader = async (selectedUser: TUserModel, isLeader: boolean) => {
try {
const { room } = this.state;
await Services.toggleRoomLeader({
roomId: room.rid,
t: room.t,
userId: selectedUser._id,
isLeader
});
const message = isLeader
? 'User__username__is_now_a_leader_of__room_name_'
: 'User__username__removed_from__room_name__leaders';
EventEmitter.emit(LISTENER, {
message: I18n.t(message, {
username: this.getUserDisplayName(selectedUser),
room_name: getRoomTitle(room)
})
});
} catch (e) {
log(e);
}
this.fetchRoomMembersRoles();
};
handleModerator = async (selectedUser: TUserModel, isModerator: boolean) => {
try {
const { room } = this.state;
await Services.toggleRoomModerator({
roomId: room.rid,
t: room.t,
userId: selectedUser._id,
isModerator
});
const message = isModerator
? 'User__username__is_now_a_moderator_of__room_name_'
: 'User__username__removed_from__room_name__moderators';
EventEmitter.emit(LISTENER, {
message: I18n.t(message, {
username: this.getUserDisplayName(selectedUser),
room_name: getRoomTitle(room)
})
});
} catch (e) {
log(e);
}
this.fetchRoomMembersRoles();
};
handleIgnore = async (selectedUser: TUserModel, ignore: boolean) => {
try {
const { room } = this.state;
await Services.ignoreUser({
rid: room.rid,
userId: selectedUser._id,
ignore
});
const message = I18n.t(ignore ? 'User_has_been_ignored' : 'User_has_been_unignored');
EventEmitter.emit(LISTENER, { message });
} catch (e) {
log(e);
}
};
handleRemoveUserFromRoom = async (selectedUser: TUserModel) => {
try {
const { room, members, membersFiltered } = this.state;
const userId = selectedUser._id;
// TODO: interface SubscriptionType on IRoom is wrong
await Services.removeUserFromRoom({ roomId: room.rid, t: room.t as RoomTypes, userId });
const message = I18n.t('User_has_been_removed_from_s', { s: getRoomTitle(room) });
EventEmitter.emit(LISTENER, { message });
this.setState({
members: members.filter(member => member._id !== userId),
membersFiltered: this.isServerVersionLowerThan3_16 ? membersFiltered.filter(member => member._id !== userId) : []
});
} catch (e) {
log(e);
}
};
renderSearchBar = () => <SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='room-members-view-search' />;
renderItem = ({ item }: { item: TUserModel }) => {
const { theme } = this.props;
return (
<UserItem
name={item.name as string}
username={item.username}
onPress={() => this.onPressUser(item)}
testID={`room-members-view-item-${item.username}`}
theme={theme}
return (
<SafeAreaView testID='room-members-view'>
<StatusBar />
<FlatList
data={filteredMembers || state.members}
renderItem={({ item }) => (
<View style={{ backgroundColor: colors.backgroundColor }}>
<UserItem
name={item.name as string}
username={item.username}
onPress={() => onPressUser(item)}
testID={`room-members-view-item-${item.username}`}
/>
</View>
)}
style={styles.list}
keyExtractor={item => item._id}
ItemSeparatorComponent={List.Separator}
ListHeaderComponent={
<>
<ActionsSection joined={params.joined as boolean} rid={state.room.rid} t={state.room.t} />
<View style={{ backgroundColor: colors.backgroundColor }}>
<SearchBox onChangeText={text => updateState({ filter: text.trim() })} testID='room-members-view-search' />
</View>
</>
}
ListFooterComponent={() => (state.isLoading ? <ActivityIndicator /> : null)}
onEndReachedThreshold={0.1}
onEndReached={() => fetchMembers(state.allUsers)}
ListEmptyComponent={() =>
state.end ? <Text style={[styles.noResult, { color: colors.titleText }]}>{I18n.t('No_members_found')}</Text> : null
}
{...scrollPersistTaps}
/>
);
};
</SafeAreaView>
);
};
render() {
const { filtering, members, membersFiltered, isLoading } = this.state;
const { theme } = this.props;
return (
<SafeAreaView testID='room-members-view'>
<StatusBar />
<FlatList
data={!!filtering && this.isServerVersionLowerThan3_16 ? membersFiltered : members}
renderItem={this.renderItem}
style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]}
keyExtractor={item => item._id}
ItemSeparatorComponent={List.Separator}
ListHeaderComponent={this.renderSearchBar}
ListFooterComponent={() => {
if (isLoading) {
return <ActivityIndicator />;
}
return null;
}}
onEndReachedThreshold={0.1}
onEndReached={this.fetchMembers}
maxToRenderPerBatch={5}
windowSize={10}
{...scrollPersistTaps}
/>
</SafeAreaView>
);
}
}
const mapStateToProps = (state: IApplicationState) => ({
baseUrl: state.server.server,
user: getUserSelector(state),
isMasterDetail: state.app.isMasterDetail,
useRealName: state.settings.UI_Use_Real_Name,
muteUserPermission: state.permissions['mute-user'],
setLeaderPermission: state.permissions['set-leader'],
setOwnerPermission: state.permissions['set-owner'],
setModeratorPermission: state.permissions['set-moderator'],
removeUserPermission: state.permissions['remove-user'],
editTeamMemberPermission: state.permissions['edit-team-member'],
viewAllTeamChannelsPermission: state.permissions['view-all-team-channels'],
viewAllTeamsPermission: state.permissions['view-all-teams'],
serverVersion: state.server.version
});
export default connect(mapStateToProps)(withTheme(withActionSheet(RoomMembersView)));
export default RoomMembersView;

View File

@ -1,20 +1,15 @@
import { StyleSheet } from 'react-native';
import sharedStyles from '../Styles';
export default StyleSheet.create({
list: {
flex: 1
},
item: {
flexDirection: 'row',
paddingVertical: 10,
paddingHorizontal: 16,
alignItems: 'center'
},
avatar: {
marginRight: 16
},
separator: {
height: StyleSheet.hairlineWidth,
marginLeft: 60
noResult: {
fontSize: 16,
paddingVertical: 56,
...sharedStyles.textSemibold,
...sharedStyles.textAlignCenter
}
});

View File

@ -813,6 +813,10 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
};
onReplyInit = (message: TAnyMessageModel, mention: boolean) => {
// If there's a thread already, we redirect to it
if (mention && !!message.tlm) {
return this.onThreadPress(message);
}
this.setState({
selectedMessage: message,
replying: true,
@ -833,6 +837,10 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
};
onMessageLongPress = (message: TAnyMessageModel) => {
// if it's a thread message on main room, we disable the long press
if (message.tmid && !this.tmid) {
return;
}
this.messagebox?.current?.closeEmojiAndAction(this.messageActions?.showMessageActions, message);
};

View File

@ -1,296 +0,0 @@
import { Q } from '@nozbe/watermelondb';
import orderBy from 'lodash/orderBy';
import React from 'react';
import { FlatList, View } from 'react-native';
import { connect } from 'react-redux';
import { Subscription } from 'rxjs';
import { addUser, removeUser, reset } from '../actions/selectedUsers';
import { themes } from '../lib/constants';
import * as HeaderButton from '../containers/HeaderButton';
import * as List from '../containers/List';
import { sendLoadingEvent } from '../containers/Loading';
import SafeAreaView from '../containers/SafeAreaView';
import SearchBox from '../containers/SearchBox';
import StatusBar from '../containers/StatusBar';
import { IApplicationState, IBaseScreen, ISearch, ISearchLocal, IUser } from '../definitions';
import I18n from '../i18n';
import database from '../lib/database';
import UserItem from '../containers/UserItem';
import { ISelectedUser } from '../reducers/selectedUsers';
import { getUserSelector } from '../selectors/login';
import { ChatsStackParamList } from '../stacks/types';
import { withTheme } from '../theme';
import { showErrorAlert } from '../lib/methods/helpers/info';
import log, { events, logEvent } from '../lib/methods/helpers/log';
import sharedStyles from './Styles';
import { search } from '../lib/methods';
import { isGroupChat } from '../lib/methods/helpers';
const ITEM_WIDTH = 250;
const getItemLayout = (_: any, index: number) => ({ length: ITEM_WIDTH, offset: ITEM_WIDTH * index, index });
interface ISelectedUsersViewState {
maxUsers?: number;
search: (ISearch | ISearchLocal)[];
chats: ISelectedUser[];
}
interface ISelectedUsersViewProps extends IBaseScreen<ChatsStackParamList, 'SelectedUsersView'> {
users: ISelectedUser[];
loading: boolean;
user: IUser;
baseUrl: string;
}
class SelectedUsersView extends React.Component<ISelectedUsersViewProps, ISelectedUsersViewState> {
private flatlist?: FlatList;
private querySubscription?: Subscription;
constructor(props: ISelectedUsersViewProps) {
super(props);
this.init();
const maxUsers = props.route.params?.maxUsers;
this.state = {
maxUsers,
search: [],
chats: []
};
const { user, dispatch } = this.props;
if (this.isGroupChat()) {
dispatch(addUser({ _id: user.id, name: user.username, fname: user.name as string }));
}
this.setHeader(props.route.params?.showButton);
}
componentDidUpdate(prevProps: ISelectedUsersViewProps) {
const { users, loading } = this.props;
if (this.isGroupChat()) {
if (prevProps.users.length !== users.length) {
this.setHeader(users.length > 0);
}
}
if (loading !== prevProps.loading) {
sendLoadingEvent({ visible: loading });
}
}
componentWillUnmount() {
const { dispatch } = this.props;
dispatch(reset());
if (this.querySubscription && this.querySubscription.unsubscribe) {
this.querySubscription.unsubscribe();
}
}
// showButton can be sent as route params or updated by the component
setHeader = (showButton?: boolean) => {
const { navigation, route } = this.props;
const title = route.params?.title ?? I18n.t('Select_Users');
const buttonText = route.params?.buttonText ?? I18n.t('Next');
const maxUsers = route.params?.maxUsers;
const nextAction = route.params?.nextAction ?? (() => {});
const options = {
title,
headerRight: () =>
(!maxUsers || showButton) && (
<HeaderButton.Container>
<HeaderButton.Item title={buttonText} onPress={nextAction} testID='selected-users-view-submit' />
</HeaderButton.Container>
)
};
navigation.setOptions(options);
};
// eslint-disable-next-line react/sort-comp
init = async () => {
try {
const db = database.active;
const observable = await db.get('subscriptions').query(Q.where('t', 'd')).observeWithColumns(['room_updated_at']);
this.querySubscription = observable.subscribe(data => {
const chats = orderBy(data, ['roomUpdatedAt'], ['desc']) as ISelectedUser[];
this.setState({ chats });
});
} catch (e) {
log(e);
}
};
onSearchChangeText(text: string) {
this.handleSearch(text);
}
handleSearch = async (text: string) => {
const result = await search({ text, filterRooms: false });
this.setState({
search: result
});
};
isGroupChat = () => {
const { maxUsers } = this.state;
return maxUsers && maxUsers > 2;
};
isChecked = (username: string) => {
const { users } = this.props;
return users.findIndex(el => el.name === username) !== -1;
};
toggleUser = (user: ISelectedUser) => {
const { maxUsers } = this.state;
const {
dispatch,
users,
user: { username }
} = this.props;
// Disallow removing self user from the direct message group
if (this.isGroupChat() && username === user.name) {
return;
}
if (!this.isChecked(user.name)) {
if (this.isGroupChat() && users.length === maxUsers) {
return showErrorAlert(I18n.t('Max_number_of_users_allowed_is_number', { maxUsers }), I18n.t('Oops'));
}
logEvent(events.SELECTED_USERS_ADD_USER);
dispatch(addUser(user));
} else {
logEvent(events.SELECTED_USERS_REMOVE_USER);
dispatch(removeUser(user));
}
};
_onPressItem = (id: string, item = {} as ISelectedUser) => {
if (item.search) {
this.toggleUser({ _id: item._id, name: item.username as string, fname: item.name });
} else {
this.toggleUser({ _id: item._id, name: item.name, fname: item.fname });
}
};
_onPressSelectedItem = (item: ISelectedUser) => this.toggleUser(item);
renderHeader = () => {
const { theme } = this.props;
return (
<View style={{ backgroundColor: themes[theme].backgroundColor }}>
<SearchBox onChangeText={(text: string) => this.onSearchChangeText(text)} testID='select-users-view-search' />
{this.renderSelected()}
</View>
);
};
setFlatListRef = (ref: FlatList) => (this.flatlist = ref);
onContentSizeChange = () => this.flatlist?.scrollToEnd({ animated: true });
renderSelected = () => {
const { users, theme } = this.props;
if (users.length === 0) {
return null;
}
return (
<FlatList
data={users}
ref={this.setFlatListRef}
onContentSizeChange={this.onContentSizeChange}
getItemLayout={getItemLayout}
keyExtractor={item => item._id}
style={[sharedStyles.separatorTop, { borderColor: themes[theme].separatorColor }]}
contentContainerStyle={{ marginVertical: 5 }}
renderItem={this.renderSelectedItem}
keyboardShouldPersistTaps='always'
horizontal
/>
);
};
renderSelectedItem = ({ item }: { item: ISelectedUser }) => {
const { theme } = this.props;
return (
<UserItem
name={item.fname}
username={item.name}
onPress={() => this._onPressSelectedItem(item)}
testID={`selected-user-${item.name}`}
style={{ paddingRight: 15 }}
theme={theme}
/>
);
};
renderItem = ({ item, index }: { item: ISelectedUser; index: number }) => {
const { search, chats } = this.state;
const { theme } = this.props;
const name = item.search ? item.name : item.fname;
const username = item.search ? (item.username as string) : item.name;
let style = { borderColor: themes[theme].separatorColor };
if (index === 0) {
style = { ...style, ...sharedStyles.separatorTop };
}
if (search.length > 0 && index === search.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
if (search.length === 0 && index === chats.length - 1) {
style = { ...style, ...sharedStyles.separatorBottom };
}
return (
<UserItem
name={name}
username={username}
onPress={() => this._onPressItem(item._id, item)}
testID={`select-users-view-item-${item.name}`}
icon={this.isChecked(username) ? 'check' : null}
style={style}
theme={theme}
/>
);
};
renderList = () => {
const { search, chats } = this.state;
const { theme } = this.props;
const searchOrChats = (search.length > 0 ? search : chats) as ISelectedUser[];
// filter DM between multiple users
const data = searchOrChats.filter(sub => !isGroupChat(sub));
return (
<FlatList
data={data}
extraData={this.props}
keyExtractor={item => item._id}
renderItem={this.renderItem}
ItemSeparatorComponent={List.Separator}
ListHeaderComponent={this.renderHeader}
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
keyboardShouldPersistTaps='always'
/>
);
};
render() {
return (
<SafeAreaView testID='select-users-view'>
<StatusBar />
{this.renderList()}
</SafeAreaView>
);
}
}
const mapStateToProps = (state: IApplicationState) => ({
baseUrl: state.server.server,
users: state.selectedUsers.users,
loading: state.selectedUsers.loading,
user: getUserSelector(state)
});
export default connect(mapStateToProps)(withTheme(SelectedUsersView));

View File

@ -0,0 +1,74 @@
import React, { useRef } from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
import { themes } from '../../lib/constants';
import SearchBox from '../../containers/SearchBox';
import I18n from '../../i18n';
import { ISelectedUser } from '../../reducers/selectedUsers';
import { useTheme } from '../../theme';
import sharedStyles from '../Styles';
import { useAppSelector } from '../../lib/hooks';
import Chip from '../../containers/Chip';
const styles = StyleSheet.create({
selectedText: {
marginLeft: 16,
marginBottom: 12,
fontSize: 12,
...sharedStyles.textRegular
},
contentContainerList: {
paddingHorizontal: 16,
marginBottom: 16
}
});
const Header = ({
onChangeText,
useRealName,
onPressItem
}: {
useRealName: boolean;
onChangeText: (text: string) => void;
onPressItem: (userItem: ISelectedUser) => void;
}) => {
const flatlist = useRef<FlatList>();
const { theme } = useTheme();
const { users } = useAppSelector(state => ({
users: state.selectedUsers.users
}));
const onContentSizeChange = () => flatlist?.current?.scrollToEnd({ animated: true });
return (
<View style={{ backgroundColor: themes[theme].backgroundColor }}>
<SearchBox onChangeText={(text: string) => onChangeText(text)} testID='select-users-view-search' />
{users.length === 0 ? null : (
<View>
<Text style={[styles.selectedText, { color: themes[theme].auxiliaryTintColor }]}>
{I18n.t('N_Selected_members', { n: users.length })}
</Text>
<FlatList
data={users}
ref={(ref: FlatList) => (flatlist.current = ref)}
onContentSizeChange={onContentSizeChange}
keyExtractor={item => item._id}
renderItem={({ item }) => {
const name = useRealName && item.fname ? item.fname : item.name;
const username = item.search ? (item.username as string) : item.name;
return (
<Chip text={name} avatar={username} onPress={() => onPressItem(item)} testID={`selected-user-${item.name}`} />
);
}}
keyboardShouldPersistTaps='always'
contentContainerStyle={styles.contentContainerList}
horizontal
/>
</View>
)}
</View>
);
};
export default Header;

Some files were not shown because too many files have changed in this diff Show More