Merge branch 'develop' into chore.try-flashlist
# Conflicts: # app/containers/message/index.tsx # app/views/RoomView/List/List.tsx
This commit is contained in:
commit
898a4a398b
|
@ -3,5 +3,5 @@ updates:
|
|||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 25
|
|
@ -6,11 +6,13 @@ import RNBootSplash from 'react-native-bootsplash';
|
|||
import { selectServerRequest } from '../app/actions/server';
|
||||
import { mockedStore as store } from '../app/reducers/mockedStore';
|
||||
import database from '../app/lib/database';
|
||||
import { setUser } from '../app/actions/login';
|
||||
|
||||
RNBootSplash.hide();
|
||||
|
||||
const baseUrl = 'https://open.rocket.chat';
|
||||
store.dispatch(selectServerRequest(baseUrl));
|
||||
store.dispatch(setUser({ id: 'abc', username: 'rocket.cat', name: 'Rocket Cat' }));
|
||||
database.setActiveDB(baseUrl);
|
||||
|
||||
const StorybookUIRoot = getStorybookUI({});
|
||||
|
|
|
@ -3,7 +3,13 @@ import { Provider } from 'react-redux';
|
|||
|
||||
import { themes } from '../app/lib/constants';
|
||||
import MessageContext from '../app/containers/message/Context';
|
||||
import { selectServerRequest } from '../app/actions/server';
|
||||
import { mockedStore as store } from '../app/reducers/mockedStore';
|
||||
import { setUser } from '../app/actions/login';
|
||||
|
||||
const baseUrl = 'https://open.rocket.chat';
|
||||
store.dispatch(selectServerRequest(baseUrl));
|
||||
store.dispatch(setUser({ id: 'abc', username: 'rocket.cat', name: 'Rocket Cat' }));
|
||||
|
||||
export const decorators = [
|
||||
Story => (
|
||||
|
@ -15,7 +21,7 @@ export const decorators = [
|
|||
username: 'diego.mello',
|
||||
token: 'abc'
|
||||
},
|
||||
baseUrl: 'https://open.rocket.chat',
|
||||
baseUrl,
|
||||
onPress: () => {},
|
||||
onLongPress: () => {},
|
||||
reactionInit: () => {},
|
||||
|
|
|
@ -30,6 +30,7 @@ const getStories = () => {
|
|||
require("../app/containers/markdown/new/NewMarkdown.stories.tsx"),
|
||||
require("../app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories.tsx"),
|
||||
require("../app/containers/message/Message.stories.tsx"),
|
||||
require("../app/containers/ReactionsList/ReactionsList.stories.tsx"),
|
||||
require("../app/containers/RoomHeader/RoomHeader.stories.tsx"),
|
||||
require("../app/containers/RoomItem/RoomItem.stories.tsx"),
|
||||
require("../app/containers/SearchBox/SearchBox.stories.tsx"),
|
||||
|
|
|
@ -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\\":\\"Inter\\",\\"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\\":4,\\"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\\":\\"Inter\\",\\"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\\":4,\\"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 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\\":4,\\"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 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\\":4,\\"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\\":\\"Inter\\",\\"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\\":4,\\"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\\":\\"Inter\\",\\"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\\":4,\\"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!\\"]}]}"`;
|
||||
|
|
|
@ -1,11 +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 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\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"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 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\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"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 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\\":4,\\"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 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\\":4,\\"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\\"]}]}]}]}]}"`;
|
||||
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\\":4,\\"justifyContent\\":\\"center\\",\\"maxWidth\\":192},{\\"backgroundColor\\":\\"#efeff4\\"},null],\\"collapsable\\":false},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4},{\\"marginRight\\":8,\\"marginVertical\\":8}],\\"testID\\":\\"avatar\\"},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"overflow\\":\\"hidden\\"},{\\"width\\":28,\\"height\\":28,\\"borderRadius\\":4}]},\\"children\\":[{\\"type\\":\\"FastImageView\\",\\"props\\":{\\"style\\":{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},\\"source\\":{\\"uri\\":\\"https://open.rocket.chat/avatar/rocket.cat?format=png&size=56\\",\\"headers\\":{\\"User-Agent\\":\\"RC Mobile; ios unknown; vunknown (unknown)\\"},\\"priority\\":\\"high\\"},\\"resizeMode\\":\\"cover\\"},\\"children\\":null}]}]},{\\"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
|
@ -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\\":\\"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 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\\":4,\\"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\\":4,\\"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\\":\\"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\\"]}]}]}]"`;
|
||||
exports[`Storyshots Login Services Service List 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"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\\":4,\\"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\\":4,\\"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\\":4,\\"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
|
@ -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\\":\\"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\\":[\\"\\"]}]}]}]}"`;
|
||||
exports[`Storyshots SearchBox Basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"testID\\":\\"searchbox\\",\\"style\\":{\\"backgroundColor\\":\\"#ffffff\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},{\\"margin\\":16,\\"marginBottom\\":16}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\",\\"justifyContent\\":\\"center\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"paddingHorizontal\\":16,\\"paddingVertical\\":10,\\"borderWidth\\":1,\\"borderRadius\\":4},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\\"},{\\"right\\":12}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]}]}]}]}"`;
|
||||
|
|
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
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
File diff suppressed because one or more lines are too long
|
@ -147,7 +147,7 @@ android {
|
|||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode VERSIONCODE as Integer
|
||||
versionName "4.31.0"
|
||||
versionName "4.33.0"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
if (!isFoss) {
|
||||
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
|
||||
|
@ -351,6 +351,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation project(':@react-native-community_viewpager')
|
||||
implementation "androidx.core:core-splashscreen:1.0.0"
|
||||
playImplementation project(':react-native-notifications')
|
||||
playImplementation 'com.google.firebase:firebase-core:16.0.0'
|
||||
playImplementation project(':@react-native-firebase_app')
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
|
@ -13,17 +13,8 @@
|
|||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:replace="android:allowBackup">
|
||||
<activity
|
||||
android:name="com.zoontek.rnbootsplash.RNBootSplashActivity"
|
||||
android:theme="@style/BootTheme"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
tools:replace="android:allowBackup">
|
||||
<activity
|
||||
android:name="chat.rocket.reactnative.MainActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
|
@ -56,6 +47,10 @@
|
|||
android:host="jitsi.rocket.chat"
|
||||
android:scheme="rocketchat" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="chat.rocket.reactnative.share.ShareActivity"
|
||||
|
@ -63,7 +58,7 @@
|
|||
android:label="@string/share_extension_name"
|
||||
android:noHistory="true"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/AppTheme"
|
||||
android:theme="@style/ShareTheme"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
package chat.rocket.reactnative;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.ReactActivity;
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.zoontek.rnbootsplash.RNBootSplash;
|
||||
|
||||
import expo.modules.ReactActivityDelegateWrapper;
|
||||
|
||||
import com.zoontek.rnbootsplash.RNBootSplash;
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
RNBootSplash.init(this);
|
||||
// https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704067
|
||||
super.onCreate(null);
|
||||
RNBootSplash.init(R.drawable.launch_screen, MainActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package chat.rocket.reactnative.share;
|
||||
|
||||
import com.facebook.react.ReactActivity;
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.ReactRootView;
|
||||
|
||||
public class ShareActivity extends ReactActivity {
|
||||
@Override
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
||||
<!-- the background color. it can be a system color or a custom one defined in colors.xml -->
|
||||
<item android:drawable="@color/splashBackground" />
|
||||
<item>
|
||||
<!-- the app logo, centered horizontally and vertically -->
|
||||
<bitmap
|
||||
android:src="@drawable/splash"
|
||||
android:gravity="center" />
|
||||
</item>
|
||||
</layer-list>
|
|
@ -8,25 +8,15 @@
|
|||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
</style>
|
||||
|
||||
<style name="Share.Window" parent="android:Theme">
|
||||
<item name="android:windowEnterAnimation">@null</item>
|
||||
<item name="android:windowExitAnimation">@null</item>
|
||||
<style name="ShareTheme" parent="AppTheme">
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Share.Transparent" parent="android:Theme">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@color/primary_dark</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:backgroundDimEnabled">true</item>
|
||||
<item name="android:windowAnimationStyle">@style/Share.Window</item>
|
||||
</style>
|
||||
|
||||
<style name="BootTheme" parent="AppTheme">
|
||||
<item name="android:background">@drawable/launch_screen</item>
|
||||
<item name="colorPrimaryDark">@color/splashBackground</item>
|
||||
<item name="android:navigationBarColor">@color/splashBackground</item>
|
||||
<style name="BootTheme" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/splashBackground</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
|
||||
<item name="postSplashScreenTheme">@style/AppTheme</item>
|
||||
</style>
|
||||
|
||||
<!-- https://github.com/facebook/react-native/blob/d1ab03235cb4b93304150878d2b9057ab45bba77/ReactAndroid/src/main/res/views/modal/values/themes.xml#L5 -->
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 7.5 KiB |
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
||||
<!-- the background color. it can be a system color or a custom one defined in colors.xml -->
|
||||
<item android:drawable="@color/splashBackground" />
|
||||
<item>
|
||||
<!-- the app logo, centered horizontally and vertically -->
|
||||
<bitmap
|
||||
android:src="@drawable/splash"
|
||||
android:gravity="center" />
|
||||
</item>
|
||||
</layer-list>
|
|
@ -6,7 +6,6 @@
|
|||
android:name="chat.rocket.reactnative.MainPlayApplication"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:theme="@style/AppTheme"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
tools:replace="android:name"
|
||||
>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package chat.rocket.reactnative;
|
||||
|
||||
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
|
@ -35,8 +37,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
|
||||
|
||||
public class CustomPushNotification extends PushNotification {
|
||||
public static ReactApplicationContext reactApplicationContext;
|
||||
final NotificationManager notificationManager;
|
||||
|
@ -322,7 +322,12 @@ public class CustomPushNotification extends PushNotification {
|
|||
replyIntent.setAction(KEY_REPLY);
|
||||
replyIntent.putExtra("pushNotification", bundle);
|
||||
|
||||
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
PendingIntent replyPendingIntent;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||
} else {
|
||||
replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
|
||||
.setLabel(label)
|
||||
|
@ -343,12 +348,13 @@ public class CustomPushNotification extends PushNotification {
|
|||
Intent intent = new Intent(mContext, DismissNotification.class);
|
||||
intent.putExtra(NOTIFICATION_ID, notificationId);
|
||||
|
||||
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, intent, 0);
|
||||
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
notification.setDeleteIntent(dismissPendingIntent);
|
||||
}
|
||||
|
||||
private void notificationLoad(Ejson ejson, Callback callback) {
|
||||
LoadNotification.load(reactApplicationContext, ejson, callback);
|
||||
LoadNotification loadNotification = new LoadNotification();
|
||||
loadNotification.load(reactApplicationContext, ejson, callback);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
package chat.rocket.reactnative;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.content.Context;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.Interceptor;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
class JsonResponse {
|
||||
Data data;
|
||||
|
@ -49,11 +43,11 @@ class JsonResponse {
|
|||
}
|
||||
|
||||
public class LoadNotification {
|
||||
private static int RETRY_COUNT = 0;
|
||||
private static int[] TIMEOUT = new int[]{0, 1, 3, 5, 10};
|
||||
private static String TOKEN_KEY = "reactnativemeteor_usertoken-";
|
||||
private int RETRY_COUNT = 0;
|
||||
private int[] TIMEOUT = new int[]{0, 1, 3, 5, 10};
|
||||
private String TOKEN_KEY = "reactnativemeteor_usertoken-";
|
||||
|
||||
public static void load(ReactApplicationContext reactApplicationContext, final Ejson ejson, Callback callback) {
|
||||
public void load(ReactApplicationContext reactApplicationContext, final Ejson ejson, Callback callback) {
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
HttpUrl.Builder url = HttpUrl.parse(ejson.serverURL().concat("/api/v1/push.get")).newBuilder();
|
||||
|
||||
|
@ -73,7 +67,7 @@ public class LoadNotification {
|
|||
runRequest(client, request, callback);
|
||||
}
|
||||
|
||||
private static void runRequest(OkHttpClient client, Request request, Callback callback) {
|
||||
private void runRequest(OkHttpClient client, Request request, Callback callback) {
|
||||
try {
|
||||
Thread.sleep(TIMEOUT[RETRY_COUNT] * 1000);
|
||||
|
||||
|
|
|
@ -18,10 +18,10 @@ const HANDLE_HEIGHT = isIOS ? 40 : 56;
|
|||
const MIN_SNAP_HEIGHT = 16;
|
||||
const CANCEL_HEIGHT = 64;
|
||||
|
||||
const ANIMATION_DURATION = 250;
|
||||
export const ACTION_SHEET_ANIMATION_DURATION = 250;
|
||||
|
||||
const ANIMATION_CONFIG = {
|
||||
duration: ANIMATION_DURATION,
|
||||
duration: ACTION_SHEET_ANIMATION_DURATION,
|
||||
// https://easings.net/#easeInOutCubic
|
||||
easing: Easing.bezier(0.645, 0.045, 0.355, 1.0)
|
||||
};
|
||||
|
@ -140,7 +140,7 @@ const ActionSheet = React.memo(
|
|||
style={{ ...styles.container, ...bottomSheet }}
|
||||
backgroundStyle={{ backgroundColor: colors.focusedBackground }}
|
||||
onChange={index => index === -1 && onClose()}
|
||||
// We need this to allow horizontal swipe gestures inside bottom sheet like in reaction picker
|
||||
// We need this to allow horizontal swipe gesture inside the bottom sheet like in reaction picker
|
||||
enableContentPanningGesture={data?.enableContentPanningGesture ?? true}
|
||||
{...androidTablet}
|
||||
>
|
||||
|
|
|
@ -122,7 +122,6 @@ const ActionSheetContentWithInputAndSubmit = ({
|
|||
}}
|
||||
testID={testID}
|
||||
secureTextEntry={secureTextEntry}
|
||||
inputStyle={{ borderWidth: 2 }}
|
||||
bottomSheet={isIOS}
|
||||
/>
|
||||
) : null}
|
||||
|
|
|
@ -52,7 +52,11 @@ const BottomSheetContent = React.memo(({ options, hasCancel, hide, children }: I
|
|||
/>
|
||||
);
|
||||
}
|
||||
return <BottomSheetView style={styles.contentContainer}>{children}</BottomSheetView>;
|
||||
return (
|
||||
<BottomSheetView testID='action-sheet' style={styles.contentContainer}>
|
||||
{children}
|
||||
</BottomSheetView>
|
||||
);
|
||||
});
|
||||
|
||||
export default BottomSheetContent;
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export * from './Provider';
|
||||
export * from './ActionSheet';
|
||||
|
|
|
@ -52,7 +52,7 @@ export default StyleSheet.create({
|
|||
paddingHorizontal: 14,
|
||||
justifyContent: 'center',
|
||||
height: ITEM_HEIGHT,
|
||||
borderRadius: 2,
|
||||
borderRadius: 4,
|
||||
marginBottom: 12
|
||||
},
|
||||
text: {
|
||||
|
|
|
@ -43,9 +43,7 @@ const Avatar = React.memo(
|
|||
|
||||
let image;
|
||||
if (emoji) {
|
||||
image = (
|
||||
<Emoji baseUrl={server} getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />
|
||||
);
|
||||
image = <Emoji getCustomEmoji={getCustomEmoji} isMessageContainsOnlyEmoji literal={emoji} style={avatarStyle} />;
|
||||
} else {
|
||||
let uri = avatar;
|
||||
if (!isStatic) {
|
||||
|
|
|
@ -22,7 +22,7 @@ const styles = StyleSheet.create({
|
|||
paddingHorizontal: 14,
|
||||
justifyContent: 'center',
|
||||
height: 48,
|
||||
borderRadius: 2,
|
||||
borderRadius: 4,
|
||||
marginBottom: 12
|
||||
},
|
||||
text: {
|
||||
|
|
|
@ -10,7 +10,7 @@ const styles = StyleSheet.create({
|
|||
pressable: {
|
||||
paddingHorizontal: 8,
|
||||
marginRight: 8,
|
||||
borderRadius: 2,
|
||||
borderRadius: 4,
|
||||
justifyContent: 'center',
|
||||
maxWidth: 192
|
||||
},
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
import React from 'react';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { StyleProp } from 'react-native';
|
||||
import FastImage, { ImageStyle } from 'react-native-fast-image';
|
||||
|
||||
import { ICustomEmoji } from '../../definitions/IEmoji';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import { ICustomEmoji } from '../../definitions';
|
||||
|
||||
interface ICustomEmojiProps {
|
||||
emoji: ICustomEmoji;
|
||||
style: StyleProp<ImageStyle>;
|
||||
}
|
||||
|
||||
const CustomEmoji = React.memo(
|
||||
({ baseUrl, emoji, style }: ICustomEmoji) => (
|
||||
({ emoji, style }: ICustomEmojiProps) => {
|
||||
const baseUrl = useAppSelector(state => state.share.server.server || state.server.server);
|
||||
return (
|
||||
<FastImage
|
||||
style={style}
|
||||
source={{
|
||||
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.content || emoji.name)}.${emoji.extension}`,
|
||||
uri: `${baseUrl}/emoji-custom/${encodeURIComponent(emoji.name)}.${emoji.extension}`,
|
||||
priority: FastImage.priority.high
|
||||
}}
|
||||
resizeMode={FastImage.resizeMode.contain}
|
||||
/>
|
||||
),
|
||||
(prevProps, nextProps) => {
|
||||
const prevEmoji = prevProps.emoji.content || prevProps.emoji.name;
|
||||
const nextEmoji = nextProps.emoji.content || nextProps.emoji.name;
|
||||
return prevEmoji === nextEmoji;
|
||||
}
|
||||
);
|
||||
},
|
||||
() => true
|
||||
);
|
||||
|
||||
export default CustomEmoji;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
import styles from './styles';
|
||||
import CustomEmoji from './CustomEmoji';
|
||||
import { IEmoji } from '../../definitions/IEmoji';
|
||||
|
||||
interface IEmojiProps {
|
||||
emoji: IEmoji;
|
||||
}
|
||||
|
||||
export const Emoji = ({ emoji }: IEmojiProps): React.ReactElement => {
|
||||
if (typeof emoji === 'string') {
|
||||
return <Text style={styles.categoryEmoji}>{shortnameToUnicode(`:${emoji}:`)}</Text>;
|
||||
}
|
||||
return <CustomEmoji style={styles.customCategoryEmoji} emoji={emoji} />;
|
||||
};
|
|
@ -1,75 +1,45 @@
|
|||
import React from 'react';
|
||||
import { FlatList, Text, TouchableOpacity } from 'react-native';
|
||||
import { useWindowDimensions } from 'react-native';
|
||||
import { FlatList } from 'react-native-gesture-handler';
|
||||
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
import styles from './styles';
|
||||
import CustomEmoji from './CustomEmoji';
|
||||
import { EMOJI_BUTTON_SIZE } from './styles';
|
||||
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||
import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji';
|
||||
import { IEmoji } from '../../definitions/IEmoji';
|
||||
import { PressableEmoji } from './PressableEmoji';
|
||||
|
||||
const EMOJI_SIZE = 50;
|
||||
interface IEmojiCategoryProps {
|
||||
emojis: IEmoji[];
|
||||
onEmojiSelected: (emoji: IEmoji) => void;
|
||||
tabLabel?: string; // needed for react-native-scrollable-tab-view only
|
||||
}
|
||||
|
||||
const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => {
|
||||
if (emoji && emoji.isCustom) {
|
||||
return (
|
||||
<CustomEmoji
|
||||
style={[styles.customCategoryEmoji, { height: size - 16, width: size - 16 }]}
|
||||
emoji={emoji}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Text style={[styles.categoryEmoji, { height: size, width: size, fontSize: size - 14 }]}>
|
||||
{shortnameToUnicode(`:${emoji}:`)}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
const EmojiCategory = ({ onEmojiSelected, emojis }: IEmojiCategoryProps): React.ReactElement | null => {
|
||||
const { width } = useWindowDimensions();
|
||||
|
||||
class EmojiCategory extends React.Component<IEmojiCategory> {
|
||||
renderItem(emoji: IEmoji) {
|
||||
const { baseUrl, onEmojiSelected } = this.props;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
// @ts-ignore
|
||||
key={emoji && emoji.isCustom ? emoji.content : emoji}
|
||||
onPress={() => onEmojiSelected(emoji)}
|
||||
testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`}
|
||||
>
|
||||
{renderEmoji(emoji, EMOJI_SIZE, baseUrl)}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
const numColumns = Math.trunc(width / EMOJI_BUTTON_SIZE);
|
||||
const marginHorizontal = (width % EMOJI_BUTTON_SIZE) / 2;
|
||||
|
||||
render() {
|
||||
const { emojis, width } = this.props;
|
||||
const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={onEmojiSelected} />;
|
||||
|
||||
if (!width) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const numColumns = Math.trunc(width / EMOJI_SIZE);
|
||||
const marginHorizontal = (width - numColumns * EMOJI_SIZE) / 2;
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={{ marginHorizontal }}
|
||||
// rerender FlatList in case of width changes
|
||||
// needed to update the numColumns when the width changes
|
||||
key={`emoji-category-${width}`}
|
||||
// @ts-ignore
|
||||
keyExtractor={item => (item && item.isCustom && item.content) || item}
|
||||
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
|
||||
data={emojis}
|
||||
extraData={this.props}
|
||||
renderItem={({ item }) => this.renderItem(item)}
|
||||
renderItem={renderItem}
|
||||
numColumns={numColumns}
|
||||
initialNumToRender={45}
|
||||
removeClippedSubviews
|
||||
contentContainerStyle={{ marginHorizontal }}
|
||||
{...scrollPersistTaps}
|
||||
keyboardDismissMode={'none'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default EmojiCategory;
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
import React, { useState } from 'react';
|
||||
import { StyleSheet, TextInputProps } from 'react-native';
|
||||
|
||||
import { FormTextInput } from '../TextInput/FormTextInput';
|
||||
import { useTheme } from '../../theme';
|
||||
import I18n from '../../i18n';
|
||||
import { isIOS } from '../../lib/methods/helpers';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
input: {
|
||||
height: 32,
|
||||
borderWidth: 0,
|
||||
paddingVertical: 0,
|
||||
borderRadius: 4
|
||||
},
|
||||
textInputContainer: {
|
||||
marginBottom: 0
|
||||
}
|
||||
});
|
||||
|
||||
interface IEmojiSearchBarProps {
|
||||
onBlur?: TextInputProps['onBlur'];
|
||||
onChangeText: TextInputProps['onChangeText'];
|
||||
bottomSheet?: boolean;
|
||||
}
|
||||
|
||||
export const EmojiSearch = ({ onBlur, onChangeText, bottomSheet }: IEmojiSearchBarProps): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
|
||||
const handleTextChange = (text: string) => {
|
||||
setSearchText(text);
|
||||
if (onChangeText) {
|
||||
onChangeText(text);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormTextInput
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
autoComplete='off'
|
||||
returnKeyType='search'
|
||||
textContentType='none'
|
||||
blurOnSubmit
|
||||
placeholder={I18n.t('Search_emoji')}
|
||||
placeholderTextColor={colors.auxiliaryText}
|
||||
underlineColorAndroid='transparent'
|
||||
onChangeText={handleTextChange}
|
||||
inputStyle={[styles.input, { backgroundColor: colors.textInputSecondaryBackground }]}
|
||||
containerStyle={styles.textInputContainer}
|
||||
value={searchText}
|
||||
onClearInput={() => handleTextChange('')}
|
||||
onBlur={onBlur}
|
||||
iconRight={'search'}
|
||||
testID='emoji-searchbar-input'
|
||||
bottomSheet={bottomSheet && isIOS}
|
||||
autoFocus={!bottomSheet} // focus on input when not in reaction picker
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import { View, Pressable } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import styles from './styles';
|
||||
import { IFooterProps } from './interfaces';
|
||||
|
||||
const BUTTON_HIT_SLOP = { top: 15, right: 15, bottom: 15, left: 15 };
|
||||
|
||||
const Footer = ({ onSearchPressed, onBackspacePressed }: IFooterProps): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<View style={[styles.footerContainer, { borderTopColor: colors.borderColor }]}>
|
||||
<Pressable
|
||||
onPress={onSearchPressed}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
style={({ pressed }) => [styles.footerButtonsContainer, { opacity: pressed ? 0.7 : 1 }]}
|
||||
testID='emoji-picker-search'
|
||||
>
|
||||
<CustomIcon color={colors.auxiliaryTintColor} size={24} name='search' />
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
onPress={onBackspacePressed}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
style={({ pressed }) => [styles.footerButtonsContainer, { opacity: pressed ? 0.7 : 1 }]}
|
||||
testID='emoji-picker-backspace'
|
||||
>
|
||||
<CustomIcon color={colors.auxiliaryTintColor} size={24} name='backspace' />
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { Pressable } from 'react-native';
|
||||
|
||||
import styles, { EMOJI_BUTTON_SIZE } from './styles';
|
||||
import { IEmoji } from '../../definitions/IEmoji';
|
||||
import { useTheme } from '../../theme';
|
||||
import { isIOS } from '../../lib/methods/helpers';
|
||||
import { Emoji } from './Emoji';
|
||||
|
||||
export const PressableEmoji = ({ emoji, onPress }: { emoji: IEmoji; onPress: (emoji: IEmoji) => void }): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<Pressable
|
||||
key={typeof emoji === 'string' ? emoji : emoji.name}
|
||||
onPress={() => onPress(emoji)}
|
||||
testID={`emoji-${typeof emoji === 'string' ? emoji : emoji.name}`}
|
||||
android_ripple={{ color: colors.bannerBackground, borderless: true, radius: EMOJI_BUTTON_SIZE / 2 }}
|
||||
style={({ pressed }: { pressed: boolean }) => [
|
||||
styles.emojiButton,
|
||||
{
|
||||
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Emoji emoji={emoji} />
|
||||
</Pressable>
|
||||
);
|
||||
};
|
|
@ -1,56 +1,42 @@
|
|||
import React from 'react';
|
||||
import { StyleProp, Text, TextStyle, TouchableOpacity, View } from 'react-native';
|
||||
import { Pressable, View } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { useTheme } from '../../theme';
|
||||
import { ITabBarProps } from './interfaces';
|
||||
import { isIOS } from '../../lib/methods/helpers';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
|
||||
interface ITabBarProps {
|
||||
goToPage?: (page: number) => void;
|
||||
activeTab?: number;
|
||||
tabs?: string[];
|
||||
tabEmojiStyle: StyleProp<TextStyle>;
|
||||
theme: TSupportedThemes;
|
||||
}
|
||||
|
||||
export default class TabBar extends React.Component<ITabBarProps> {
|
||||
shouldComponentUpdate(nextProps: ITabBarProps) {
|
||||
const { activeTab, theme } = this.props;
|
||||
if (nextProps.activeTab !== activeTab) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { tabs, goToPage, tabEmojiStyle, activeTab, theme } = this.props;
|
||||
const TabBar = ({ activeTab, tabs, goToPage }: ITabBarProps): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
<View style={styles.tabsContainer}>
|
||||
{tabs?.map((tab, i) => (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
<Pressable
|
||||
key={tab}
|
||||
onPress={() => {
|
||||
if (goToPage) {
|
||||
goToPage(i);
|
||||
onPress={() => goToPage?.(i)}
|
||||
testID={`emoji-picker-tab-${tab}`}
|
||||
android_ripple={{ color: colors.bannerBackground }}
|
||||
style={({ pressed }: { pressed: boolean }) => [
|
||||
styles.tab,
|
||||
{
|
||||
backgroundColor: isIOS && pressed ? colors.bannerBackground : 'transparent'
|
||||
}
|
||||
}}
|
||||
style={styles.tab}
|
||||
testID={`reaction-picker-${tab}`}
|
||||
]}
|
||||
>
|
||||
<Text style={[styles.tabEmoji, tabEmojiStyle]}>{tab}</Text>
|
||||
{activeTab === i ? (
|
||||
<View style={[styles.activeTabLine, { backgroundColor: themes[theme].tintColor }]} />
|
||||
) : (
|
||||
<View style={styles.tabLine} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
<CustomIcon name={tab} size={24} color={activeTab === i ? colors.tintColor : colors.auxiliaryTintColor} />
|
||||
<View
|
||||
style={
|
||||
activeTab === i
|
||||
? [styles.activeTabLine, { backgroundColor: colors.tintColor }]
|
||||
: [styles.tabLine, { backgroundColor: colors.borderColor }]
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default TabBar;
|
||||
|
|
|
@ -1,155 +1,44 @@
|
|||
import React, { Component } from 'react';
|
||||
import { StyleProp, TextStyle, View } from 'react-native';
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||
import { dequal } from 'dequal';
|
||||
import { connect } from 'react-redux';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { ImageStyle } from 'react-native-fast-image';
|
||||
|
||||
import TabBar from './TabBar';
|
||||
import EmojiCategory from './EmojiCategory';
|
||||
import Footer from './Footer';
|
||||
import styles from './styles';
|
||||
import categories from './categories';
|
||||
import database from '../../lib/database';
|
||||
import { emojisByCategory } from './emojis';
|
||||
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
import log from '../../lib/methods/helpers/log';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { IEmoji, TGetCustomEmoji, IApplicationState, ICustomEmojis, TFrequentlyUsedEmojiModel } from '../../definitions';
|
||||
import { categories, emojisByCategory } from '../../lib/constants';
|
||||
import { useTheme } from '../../theme';
|
||||
import { IEmoji, ICustomEmojis } from '../../definitions';
|
||||
import { useAppSelector, useFrequentlyUsedEmoji } from '../../lib/hooks';
|
||||
import { addFrequentlyUsed } from '../../lib/methods';
|
||||
import { IEmojiPickerProps, EventTypes } from './interfaces';
|
||||
|
||||
interface IEmojiPickerProps {
|
||||
isMessageContainsOnlyEmoji?: boolean;
|
||||
getCustomEmoji?: TGetCustomEmoji;
|
||||
baseUrl: string;
|
||||
customEmojis: ICustomEmojis;
|
||||
style?: StyleProp<ImageStyle>;
|
||||
theme: TSupportedThemes;
|
||||
onEmojiSelected: (emoji: string, shortname?: string) => void;
|
||||
tabEmojiStyle?: StyleProp<TextStyle>;
|
||||
}
|
||||
const EmojiPicker = ({
|
||||
onItemClicked,
|
||||
isEmojiKeyboard = false,
|
||||
searching = false,
|
||||
searchedEmojis = []
|
||||
}: IEmojiPickerProps): React.ReactElement | null => {
|
||||
const { colors } = useTheme();
|
||||
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji();
|
||||
|
||||
interface IEmojiPickerState {
|
||||
frequentlyUsed: (string | { content?: string; extension?: string; isCustom: boolean })[];
|
||||
customEmojis: any;
|
||||
show: boolean;
|
||||
width: number | null;
|
||||
}
|
||||
|
||||
class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
||||
constructor(props: IEmojiPickerProps) {
|
||||
super(props);
|
||||
const customEmojis = Object.keys(props.customEmojis)
|
||||
.filter(item => item === props.customEmojis[item].name)
|
||||
const allCustomEmojis: ICustomEmojis = useAppSelector(
|
||||
state => state.customEmojis,
|
||||
() => true
|
||||
);
|
||||
const customEmojis = Object.keys(allCustomEmojis)
|
||||
.filter(item => item === allCustomEmojis[item].name)
|
||||
.map(item => ({
|
||||
content: props.customEmojis[item].name,
|
||||
extension: props.customEmojis[item].extension,
|
||||
isCustom: true
|
||||
name: allCustomEmojis[item].name,
|
||||
extension: allCustomEmojis[item].extension
|
||||
}));
|
||||
this.state = {
|
||||
frequentlyUsed: [],
|
||||
customEmojis,
|
||||
show: false,
|
||||
width: null
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
await this.updateFrequentlyUsed();
|
||||
this.setState({ show: true });
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: IEmojiPickerProps, nextState: IEmojiPickerState) {
|
||||
const { frequentlyUsed, show, width } = this.state;
|
||||
const { theme } = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.show !== show) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.width !== width) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(nextState.frequentlyUsed, frequentlyUsed)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
onEmojiSelected = (emoji: IEmoji) => {
|
||||
try {
|
||||
const { onEmojiSelected } = this.props;
|
||||
if (emoji.isCustom) {
|
||||
this._addFrequentlyUsed({
|
||||
content: emoji.content,
|
||||
extension: emoji.extension,
|
||||
isCustom: true
|
||||
});
|
||||
onEmojiSelected(`:${emoji.content}:`);
|
||||
} else {
|
||||
const content = emoji;
|
||||
this._addFrequentlyUsed({ content, isCustom: false });
|
||||
const shortname = `:${emoji}:`;
|
||||
onEmojiSelected(shortnameToUnicode(shortname), shortname);
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
const handleEmojiSelect = (emoji: IEmoji) => {
|
||||
onItemClicked(EventTypes.EMOJI_PRESSED, emoji);
|
||||
addFrequentlyUsed(emoji);
|
||||
};
|
||||
|
||||
_addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => {
|
||||
const db = database.active;
|
||||
const freqEmojiCollection = db.get('frequently_used_emojis');
|
||||
let freqEmojiRecord: TFrequentlyUsedEmojiModel;
|
||||
try {
|
||||
freqEmojiRecord = await freqEmojiCollection.find(emoji.content);
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
await db.write(async () => {
|
||||
if (freqEmojiRecord) {
|
||||
await freqEmojiRecord.update(f => {
|
||||
if (f.count) {
|
||||
f.count += 1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await freqEmojiCollection.create(f => {
|
||||
f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema);
|
||||
Object.assign(f, emoji);
|
||||
f.count = 1;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
updateFrequentlyUsed = async () => {
|
||||
const db = database.active;
|
||||
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
|
||||
const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
|
||||
const frequentlyUsed = frequentlyUsedOrdered.map(item => {
|
||||
if (item.isCustom) {
|
||||
return { content: item.content, extension: item.extension, isCustom: item.isCustom };
|
||||
}
|
||||
return shortnameToUnicode(`${item.content}`);
|
||||
});
|
||||
this.setState({ frequentlyUsed });
|
||||
};
|
||||
|
||||
onLayout = ({
|
||||
nativeEvent: {
|
||||
layout: { width }
|
||||
}
|
||||
}: any) => this.setState({ width });
|
||||
|
||||
renderCategory(category: keyof typeof emojisByCategory, i: number, label: string) {
|
||||
const { frequentlyUsed, customEmojis, width } = this.state;
|
||||
const { baseUrl } = this.props;
|
||||
|
||||
const renderCategory = (category: keyof typeof emojisByCategory, i: number, label: string) => {
|
||||
let emojis = [];
|
||||
if (i === 0) {
|
||||
emojis = frequentlyUsed;
|
||||
|
@ -158,49 +47,40 @@ class EmojiPicker extends Component<IEmojiPickerProps, IEmojiPickerState> {
|
|||
} else {
|
||||
emojis = emojisByCategory[category];
|
||||
}
|
||||
return (
|
||||
<EmojiCategory
|
||||
emojis={emojis}
|
||||
onEmojiSelected={(emoji: IEmoji) => this.onEmojiSelected(emoji)}
|
||||
style={styles.categoryContainer}
|
||||
width={width}
|
||||
baseUrl={baseUrl}
|
||||
tabLabel={label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { show, frequentlyUsed } = this.state;
|
||||
const { tabEmojiStyle, theme } = this.props;
|
||||
|
||||
if (!show) {
|
||||
if (!emojis.length) {
|
||||
return null;
|
||||
}
|
||||
return <EmojiCategory emojis={emojis} onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} tabLabel={label} />;
|
||||
};
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View onLayout={this.onLayout} style={{ flex: 1 }}>
|
||||
<View style={styles.emojiPickerContainer}>
|
||||
{searching ? (
|
||||
<EmojiCategory emojis={searchedEmojis} onEmojiSelected={(emoji: IEmoji) => handleEmojiSelect(emoji)} />
|
||||
) : (
|
||||
<ScrollableTabView
|
||||
renderTabBar={() => <TabBar tabEmojiStyle={tabEmojiStyle} theme={theme} />}
|
||||
renderTabBar={() => <TabBar />}
|
||||
contentProps={{
|
||||
keyboardShouldPersistTaps: 'always',
|
||||
keyboardDismissMode: 'none'
|
||||
}}
|
||||
style={{ backgroundColor: themes[theme].focusedBackground }}
|
||||
style={{ backgroundColor: colors.messageboxBackground }}
|
||||
>
|
||||
{categories.tabs.map((tab: any, i) =>
|
||||
i === 0 && frequentlyUsed.length === 0
|
||||
? null // when no frequentlyUsed don't show the tab
|
||||
: this.renderCategory(tab.category, i, tab.tabLabel)
|
||||
)}
|
||||
{categories.tabs.map((tab: any, i) => renderCategory(tab.category, i, tab.tabLabel))}
|
||||
</ScrollableTabView>
|
||||
)}
|
||||
{isEmojiKeyboard && (
|
||||
<Footer
|
||||
onSearchPressed={() => onItemClicked(EventTypes.SEARCH_PRESSED)}
|
||||
onBackspacePressed={() => onItemClicked(EventTypes.BACKSPACE_PRESSED)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
customEmojis: state.customEmojis,
|
||||
baseUrl: state.share.server.server || state.server.server
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(EmojiPicker);
|
||||
export default EmojiPicker;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { TIconsName } from '../CustomIcon';
|
||||
import { IEmoji } from '../../definitions';
|
||||
|
||||
export enum EventTypes {
|
||||
EMOJI_PRESSED = 'emojiPressed',
|
||||
BACKSPACE_PRESSED = 'backspacePressed',
|
||||
SEARCH_PRESSED = 'searchPressed'
|
||||
}
|
||||
|
||||
export interface IEmojiPickerProps {
|
||||
onItemClicked: (event: EventTypes, emoji?: IEmoji) => void;
|
||||
isEmojiKeyboard?: boolean;
|
||||
searching?: boolean;
|
||||
searchedEmojis?: IEmoji[];
|
||||
}
|
||||
|
||||
export interface IFooterProps {
|
||||
onBackspacePressed: () => void;
|
||||
onSearchPressed: () => void;
|
||||
}
|
||||
|
||||
export interface ITabBarProps {
|
||||
goToPage?: (page: number) => void;
|
||||
activeTab?: number;
|
||||
tabs?: TIconsName[];
|
||||
}
|
|
@ -2,20 +2,23 @@ import { StyleSheet } from 'react-native';
|
|||
|
||||
import sharedStyles from '../../views/Styles';
|
||||
|
||||
export const EMOJI_BUTTON_SIZE = 44;
|
||||
export const EMOJI_SIZE = EMOJI_BUTTON_SIZE - 16;
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
tabsContainer: {
|
||||
height: 45,
|
||||
flexDirection: 'row',
|
||||
paddingTop: 5
|
||||
height: EMOJI_BUTTON_SIZE,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
tab: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingBottom: 10
|
||||
paddingVertical: 10,
|
||||
width: EMOJI_BUTTON_SIZE
|
||||
},
|
||||
tabEmoji: {
|
||||
fontSize: 20,
|
||||
|
@ -33,7 +36,6 @@ export default StyleSheet.create({
|
|||
left: 0,
|
||||
right: 0,
|
||||
height: 2,
|
||||
backgroundColor: 'rgba(0,0,0,0.05)',
|
||||
bottom: 0
|
||||
},
|
||||
categoryContainer: {
|
||||
|
@ -49,10 +51,34 @@ export default StyleSheet.create({
|
|||
},
|
||||
categoryEmoji: {
|
||||
...sharedStyles.textAlignCenter,
|
||||
textAlignVertical: 'center',
|
||||
fontSize: EMOJI_SIZE,
|
||||
backgroundColor: 'transparent',
|
||||
color: '#ffffff'
|
||||
},
|
||||
customCategoryEmoji: {
|
||||
margin: 8
|
||||
}
|
||||
height: EMOJI_SIZE,
|
||||
width: EMOJI_SIZE
|
||||
},
|
||||
emojiButton: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: EMOJI_BUTTON_SIZE,
|
||||
width: EMOJI_BUTTON_SIZE
|
||||
},
|
||||
footerContainer: {
|
||||
height: EMOJI_BUTTON_SIZE,
|
||||
paddingHorizontal: 12,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderTopWidth: 1
|
||||
},
|
||||
footerButtonsContainer: {
|
||||
height: EMOJI_BUTTON_SIZE,
|
||||
width: EMOJI_BUTTON_SIZE,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
emojiPickerContainer: { flex: 1 }
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { PADDING_HORIZONTAL } from './constants';
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingBottom: 12,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: PADDING_HORIZONTAL
|
||||
},
|
||||
title: {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Header } from '.';
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginVertical: 16
|
||||
marginBottom: 16
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -2,6 +2,6 @@ import { StyleSheet } from 'react-native';
|
|||
|
||||
export const styles = StyleSheet.create({
|
||||
contentContainerStyleFlatList: {
|
||||
paddingVertical: 32
|
||||
paddingVertical: 16
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ import sharedStyles from '../../views/Styles';
|
|||
|
||||
export const BUTTON_HEIGHT = 48;
|
||||
export const SERVICE_HEIGHT = 58;
|
||||
export const BORDER_RADIUS = 2;
|
||||
export const BORDER_RADIUS = 4;
|
||||
export const SERVICES_COLLAPSED_HEIGHT = 174;
|
||||
|
||||
export default StyleSheet.create({
|
||||
|
|
|
@ -1,32 +1,29 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { FlatList, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
import { TSupportedThemes, useTheme } from '../../theme';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
import { addFrequentlyUsed } from '../../lib/methods';
|
||||
import { useFrequentlyUsedEmoji } from '../../lib/hooks';
|
||||
import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
||||
import database from '../../lib/database';
|
||||
import { useDimensions } from '../../dimensions';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import { TAnyMessageModel, TFrequentlyUsedEmojiModel } from '../../definitions';
|
||||
import { IEmoji, TAnyMessageModel } from '../../definitions';
|
||||
import Touch from '../Touch';
|
||||
|
||||
type TItem = TFrequentlyUsedEmojiModel | string;
|
||||
|
||||
export interface IHeader {
|
||||
handleReaction: (emoji: TItem, message: TAnyMessageModel) => void;
|
||||
server: string;
|
||||
handleReaction: (emoji: IEmoji, message: TAnyMessageModel) => void;
|
||||
message: TAnyMessageModel;
|
||||
isMasterDetail: boolean;
|
||||
}
|
||||
|
||||
type TOnReaction = ({ emoji }: { emoji: TItem }) => void;
|
||||
type TOnReaction = ({ emoji }: { emoji: IEmoji }) => void;
|
||||
|
||||
interface THeaderItem {
|
||||
item: TItem;
|
||||
item: IEmoji;
|
||||
onReaction: TOnReaction;
|
||||
server: string;
|
||||
theme: TSupportedThemes;
|
||||
}
|
||||
|
||||
|
@ -64,30 +61,19 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const keyExtractor = (item: TItem) => {
|
||||
const emojiModel = item as TFrequentlyUsedEmojiModel;
|
||||
return (emojiModel.id ? emojiModel.content : item) as string;
|
||||
};
|
||||
|
||||
const DEFAULT_EMOJIS = ['clap', '+1', 'heart_eyes', 'grinning', 'thinking_face', 'smiley'];
|
||||
|
||||
const HeaderItem = ({ item, onReaction, server, theme }: THeaderItem) => {
|
||||
const emojiModel = item as TFrequentlyUsedEmojiModel;
|
||||
const emoji = (emojiModel.id ? emojiModel.content : item) as string;
|
||||
return (
|
||||
const HeaderItem = ({ item, onReaction, theme }: THeaderItem) => (
|
||||
<Touch
|
||||
testID={`message-actions-emoji-${emoji}`}
|
||||
onPress={() => onReaction({ emoji: `:${emoji}:` })}
|
||||
testID={`message-actions-emoji-${item}`}
|
||||
onPress={() => onReaction({ emoji: item })}
|
||||
style={[styles.headerItem, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
||||
>
|
||||
{emojiModel?.isCustom ? (
|
||||
<CustomEmoji style={styles.customEmoji} emoji={emojiModel} baseUrl={server} />
|
||||
{typeof item === 'string' ? (
|
||||
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${item}:`)}</Text>
|
||||
) : (
|
||||
<Text style={styles.headerIcon}>{shortnameToUnicode(`:${emoji}:`)}</Text>
|
||||
<CustomEmoji style={styles.customEmoji} emoji={item} />
|
||||
)}
|
||||
</Touch>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
|
||||
<Touch
|
||||
|
@ -99,49 +85,35 @@ const HeaderFooter = ({ onReaction, theme }: THeaderFooter) => (
|
|||
</Touch>
|
||||
);
|
||||
|
||||
const Header = React.memo(({ handleReaction, server, message, isMasterDetail }: IHeader) => {
|
||||
const [items, setItems] = useState<TItem[]>([]);
|
||||
const Header = React.memo(({ handleReaction, message, isMasterDetail }: IHeader) => {
|
||||
const { width, height } = useDimensions();
|
||||
const { theme } = useTheme();
|
||||
|
||||
// TODO: create custom hook to re-render based on screen size
|
||||
const setEmojis = async () => {
|
||||
try {
|
||||
const db = database.active;
|
||||
const freqEmojiCollection = db.get('frequently_used_emojis');
|
||||
let freqEmojis: TItem[] = await freqEmojiCollection.query().fetch();
|
||||
|
||||
const { frequentlyUsed, loaded } = useFrequentlyUsedEmoji(true);
|
||||
const isLandscape = width > height;
|
||||
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
|
||||
const quantity = size / (ITEM_SIZE + ITEM_MARGIN * 2) - 1;
|
||||
const quantity = Math.trunc(size / (ITEM_SIZE + ITEM_MARGIN * 2) - 1);
|
||||
|
||||
freqEmojis = freqEmojis.concat(DEFAULT_EMOJIS).slice(0, quantity);
|
||||
setItems(freqEmojis);
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
const onReaction: TOnReaction = ({ emoji }) => {
|
||||
handleReaction(emoji, message);
|
||||
addFrequentlyUsed(emoji);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setEmojis();
|
||||
}, []);
|
||||
|
||||
const onReaction: TOnReaction = ({ emoji }) => handleReaction(emoji, message);
|
||||
|
||||
const renderItem = ({ item }: { item: TItem }) => (
|
||||
<HeaderItem item={item} onReaction={onReaction} server={server} theme={theme} />
|
||||
);
|
||||
const renderItem = ({ item }: { item: IEmoji }) => <HeaderItem item={item} onReaction={onReaction} theme={theme} />;
|
||||
|
||||
const renderFooter = () => <HeaderFooter onReaction={onReaction} theme={theme} />;
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: themes[theme].focusedBackground }]}>
|
||||
<FlatList
|
||||
data={items}
|
||||
data={frequentlyUsed.slice(0, quantity)}
|
||||
renderItem={renderItem}
|
||||
ListFooterComponent={renderFooter}
|
||||
style={{ backgroundColor: themes[theme].focusedBackground }}
|
||||
keyExtractor={keyExtractor}
|
||||
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
scrollEnabled={false}
|
||||
horizontal
|
||||
|
|
|
@ -12,12 +12,12 @@ import { getMessageTranslation } from '../message/utils';
|
|||
import { LISTENER } from '../Toast';
|
||||
import EventEmitter from '../../lib/methods/helpers/events';
|
||||
import { showConfirmationAlert } from '../../lib/methods/helpers/info';
|
||||
import { TActionSheetOptionsItem, useActionSheet } from '../ActionSheet';
|
||||
import { TActionSheetOptionsItem, useActionSheet, ACTION_SHEET_ANIMATION_DURATION } from '../ActionSheet';
|
||||
import Header, { HEADER_HEIGHT, IHeader } from './Header';
|
||||
import events from '../../lib/methods/helpers/log/events';
|
||||
import { IApplicationState, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
||||
import { IApplicationState, IEmoji, ILoggedUser, TAnyMessageModel, TSubscriptionModel } from '../../definitions';
|
||||
import { getPermalinkMessage } from '../../lib/methods';
|
||||
import { hasPermission } from '../../lib/methods/helpers';
|
||||
import { getRoomTitle, getUidDirectMessage, hasPermission } from '../../lib/methods/helpers';
|
||||
import { Services } from '../../lib/services';
|
||||
|
||||
export interface IMessageActionsProps {
|
||||
|
@ -26,7 +26,7 @@ export interface IMessageActionsProps {
|
|||
user: Pick<ILoggedUser, 'id'>;
|
||||
editInit: (message: TAnyMessageModel) => void;
|
||||
reactionInit: (message: TAnyMessageModel) => void;
|
||||
onReactionPress: (shortname: string, messageId: string) => void;
|
||||
onReactionPress: (shortname: IEmoji, messageId: string) => void;
|
||||
replyInit: (message: TAnyMessageModel, mention: boolean) => void;
|
||||
isMasterDetail: boolean;
|
||||
isReadOnly: boolean;
|
||||
|
@ -37,11 +37,12 @@ export interface IMessageActionsProps {
|
|||
Message_AllowPinning?: boolean;
|
||||
Message_AllowStarring?: boolean;
|
||||
Message_Read_Receipt_Store_Users?: boolean;
|
||||
server: string;
|
||||
editMessagePermission?: string[];
|
||||
deleteMessagePermission?: string[];
|
||||
forceDeleteMessagePermission?: string[];
|
||||
deleteOwnMessagePermission?: string[];
|
||||
pinMessagePermission?: string[];
|
||||
createDirectMessagePermission?: string[];
|
||||
}
|
||||
|
||||
export interface IMessageActions {
|
||||
|
@ -60,7 +61,6 @@ const MessageActions = React.memo(
|
|||
onReactionPress,
|
||||
replyInit,
|
||||
isReadOnly,
|
||||
server,
|
||||
Message_AllowDeleting,
|
||||
Message_AllowDeleting_BlockDeleteInMinutes,
|
||||
Message_AllowEditing,
|
||||
|
@ -72,7 +72,9 @@ const MessageActions = React.memo(
|
|||
editMessagePermission,
|
||||
deleteMessagePermission,
|
||||
forceDeleteMessagePermission,
|
||||
pinMessagePermission
|
||||
deleteOwnMessagePermission,
|
||||
pinMessagePermission,
|
||||
createDirectMessagePermission
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
|
@ -80,19 +82,27 @@ const MessageActions = React.memo(
|
|||
hasEditPermission: false,
|
||||
hasDeletePermission: false,
|
||||
hasForceDeletePermission: false,
|
||||
hasPinPermission: false
|
||||
hasPinPermission: false,
|
||||
hasDeleteOwnPermission: false
|
||||
};
|
||||
const { showActionSheet, hideActionSheet } = useActionSheet();
|
||||
|
||||
const getPermissions = async () => {
|
||||
try {
|
||||
const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission];
|
||||
const permission = [
|
||||
editMessagePermission,
|
||||
deleteMessagePermission,
|
||||
forceDeleteMessagePermission,
|
||||
pinMessagePermission,
|
||||
deleteOwnMessagePermission
|
||||
];
|
||||
const result = await hasPermission(permission, room.rid);
|
||||
permissions = {
|
||||
hasEditPermission: result[0],
|
||||
hasDeletePermission: result[1],
|
||||
hasForceDeletePermission: result[2],
|
||||
hasPinPermission: result[3]
|
||||
hasPinPermission: result[3],
|
||||
hasDeleteOwnPermission: result[4]
|
||||
};
|
||||
} catch {
|
||||
// Do nothing
|
||||
|
@ -134,7 +144,7 @@ const MessageActions = React.memo(
|
|||
if (tmid === message.id) {
|
||||
return false;
|
||||
}
|
||||
const deleteOwn = isOwn(message);
|
||||
const deleteOwn = isOwn(message) && permissions.hasDeleteOwnPermission;
|
||||
if (!(permissions.hasDeletePermission || (Message_AllowDeleting && deleteOwn) || permissions.hasForceDeletePermission)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -237,6 +247,23 @@ const MessageActions = React.memo(
|
|||
replyInit(message, false);
|
||||
};
|
||||
|
||||
const handleReplyInDM = async (message: TAnyMessageModel) => {
|
||||
if (message?.u?.username) {
|
||||
const result = await Services.createDirectMessage(message.u.username);
|
||||
if (result.success) {
|
||||
const { room } = result;
|
||||
const params = {
|
||||
rid: room.rid,
|
||||
name: getRoomTitle(room),
|
||||
t: room.t,
|
||||
roomUserId: getUidDirectMessage(room),
|
||||
replyInDM: message
|
||||
};
|
||||
Navigation.replace('RoomView', params);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleStar = async (message: TAnyMessageModel) => {
|
||||
logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR);
|
||||
try {
|
||||
|
@ -261,12 +288,10 @@ const MessageActions = React.memo(
|
|||
const handleReaction: IHeader['handleReaction'] = (shortname, message) => {
|
||||
logEvent(events.ROOM_MSG_ACTION_REACTION);
|
||||
if (shortname) {
|
||||
// TODO: evaluate unification with IEmoji
|
||||
onReactionPress(shortname as any, message.id);
|
||||
onReactionPress(shortname, message.id);
|
||||
} else {
|
||||
reactionInit(message);
|
||||
setTimeout(() => reactionInit(message), ACTION_SHEET_ANIMATION_DURATION);
|
||||
}
|
||||
// close actionSheet when click at header
|
||||
hideActionSheet();
|
||||
};
|
||||
|
||||
|
@ -327,18 +352,7 @@ const MessageActions = React.memo(
|
|||
};
|
||||
|
||||
const getOptions = (message: TAnyMessageModel) => {
|
||||
let options: TActionSheetOptionsItem[] = [];
|
||||
|
||||
// Reply
|
||||
if (!isReadOnly && !tmid) {
|
||||
options = [
|
||||
{
|
||||
title: I18n.t('Reply_in_Thread'),
|
||||
icon: 'threads',
|
||||
onPress: () => handleReply(message)
|
||||
}
|
||||
];
|
||||
}
|
||||
const options: TActionSheetOptionsItem[] = [];
|
||||
|
||||
// Quote
|
||||
if (!isReadOnly) {
|
||||
|
@ -349,21 +363,23 @@ const MessageActions = React.memo(
|
|||
});
|
||||
}
|
||||
|
||||
// Edit
|
||||
if (allowEdit(message)) {
|
||||
// Reply
|
||||
if (!isReadOnly && !tmid) {
|
||||
options.push({
|
||||
title: I18n.t('Edit'),
|
||||
icon: 'edit',
|
||||
onPress: () => handleEdit(message)
|
||||
title: I18n.t('Reply_in_Thread'),
|
||||
icon: 'threads',
|
||||
onPress: () => handleReply(message)
|
||||
});
|
||||
}
|
||||
|
||||
// Permalink
|
||||
// Reply in DM
|
||||
if (room.t !== 'd' && room.t !== 'l' && createDirectMessagePermission) {
|
||||
options.push({
|
||||
title: I18n.t('Permalink'),
|
||||
icon: 'link',
|
||||
onPress: () => handlePermalink(message)
|
||||
title: I18n.t('Reply_in_direct_message'),
|
||||
icon: 'arrow-back',
|
||||
onPress: () => handleReplyInDM(message)
|
||||
});
|
||||
}
|
||||
|
||||
// Create Discussion
|
||||
options.push({
|
||||
|
@ -372,14 +388,12 @@ const MessageActions = React.memo(
|
|||
onPress: () => handleCreateDiscussion(message)
|
||||
});
|
||||
|
||||
// Mark as unread
|
||||
if (message.u && message.u._id !== user.id) {
|
||||
// Permalink
|
||||
options.push({
|
||||
title: I18n.t('Mark_unread'),
|
||||
icon: 'flag',
|
||||
onPress: () => handleUnread(message)
|
||||
title: I18n.t('Get_link'),
|
||||
icon: 'link',
|
||||
onPress: () => handlePermalink(message)
|
||||
});
|
||||
}
|
||||
|
||||
// Copy
|
||||
options.push({
|
||||
|
@ -395,12 +409,12 @@ const MessageActions = React.memo(
|
|||
onPress: () => handleShare(message)
|
||||
});
|
||||
|
||||
// Star
|
||||
if (Message_AllowStarring) {
|
||||
// Edit
|
||||
if (allowEdit(message)) {
|
||||
options.push({
|
||||
title: I18n.t(message.starred ? 'Unstar' : 'Star'),
|
||||
icon: message.starred ? 'star-filled' : 'star',
|
||||
onPress: () => handleStar(message)
|
||||
title: I18n.t('Edit'),
|
||||
icon: 'edit',
|
||||
onPress: () => handleEdit(message)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -413,6 +427,24 @@ const MessageActions = React.memo(
|
|||
});
|
||||
}
|
||||
|
||||
// Star
|
||||
if (Message_AllowStarring) {
|
||||
options.push({
|
||||
title: I18n.t(message.starred ? 'Unstar' : 'Star'),
|
||||
icon: message.starred ? 'star-filled' : 'star',
|
||||
onPress: () => handleStar(message)
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as unread
|
||||
if (message.u && message.u._id !== user.id) {
|
||||
options.push({
|
||||
title: I18n.t('Mark_unread'),
|
||||
icon: 'flag',
|
||||
onPress: () => handleUnread(message)
|
||||
});
|
||||
}
|
||||
|
||||
// Read Receipts
|
||||
if (Message_Read_Receipt_Store_Users) {
|
||||
options.push({
|
||||
|
@ -460,7 +492,7 @@ const MessageActions = React.memo(
|
|||
headerHeight: HEADER_HEIGHT,
|
||||
customHeader:
|
||||
!isReadOnly || room.reactWhenReadOnly ? (
|
||||
<Header server={server} handleReaction={handleReaction} isMasterDetail={isMasterDetail} message={message} />
|
||||
<Header handleReaction={handleReaction} isMasterDetail={isMasterDetail} message={message} />
|
||||
) : null
|
||||
});
|
||||
};
|
||||
|
@ -483,8 +515,10 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
isMasterDetail: state.app.isMasterDetail,
|
||||
editMessagePermission: state.permissions['edit-message'],
|
||||
deleteMessagePermission: state.permissions['delete-message'],
|
||||
deleteOwnMessagePermission: state.permissions['delete-own-message'],
|
||||
forceDeleteMessagePermission: state.permissions['force-delete-message'],
|
||||
pinMessagePermission: state.permissions['pin-message']
|
||||
pinMessagePermission: state.permissions['pin-message'],
|
||||
createDirectMessagePermission: state.permissions['create-d']
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions);
|
||||
|
|
|
@ -6,22 +6,31 @@ import { Provider } from 'react-redux';
|
|||
import store from '../../lib/store';
|
||||
import EmojiPicker from '../EmojiPicker';
|
||||
import styles from './styles';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { ThemeContext, TSupportedThemes } from '../../theme';
|
||||
import { EventTypes } from '../EmojiPicker/interfaces';
|
||||
import { IEmoji } from '../../definitions';
|
||||
import { colors } from '../../lib/constants';
|
||||
|
||||
const EmojiKeyboard = ({ theme }: { theme: TSupportedThemes }) => {
|
||||
const onEmojiSelected = (emoji: string) => {
|
||||
KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji });
|
||||
const onItemClicked = (eventType: EventTypes, emoji?: IEmoji) => {
|
||||
KeyboardRegistry.onItemSelected('EmojiKeyboard', { eventType, emoji });
|
||||
};
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<ThemeContext.Provider
|
||||
value={{
|
||||
theme,
|
||||
colors: colors[theme]
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={[styles.emojiKeyboardContainer, { borderTopColor: themes[theme].borderColor }]}
|
||||
style={[styles.emojiKeyboardContainer, { borderTopColor: colors[theme].borderColor }]}
|
||||
testID='messagebox-keyboard-emoji'
|
||||
>
|
||||
<EmojiPicker onEmojiSelected={onEmojiSelected} theme={theme} />
|
||||
<EmojiPicker onItemClicked={onItemClicked} isEmojiKeyboard={true} />
|
||||
</View>
|
||||
</ThemeContext.Provider>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
import React, { useState } from 'react';
|
||||
import { View, Text, Pressable, FlatList, StyleSheet } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import I18n from '../../i18n';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import { IEmoji } from '../../definitions';
|
||||
import { useFrequentlyUsedEmoji } from '../../lib/hooks';
|
||||
import { addFrequentlyUsed, searchEmojis } from '../../lib/methods';
|
||||
import { useDebounce } from '../../lib/methods/helpers';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import { PressableEmoji } from '../EmojiPicker/PressableEmoji';
|
||||
import { EmojiSearch } from '../EmojiPicker/EmojiSearch';
|
||||
import { EMOJI_BUTTON_SIZE } from '../EmojiPicker/styles';
|
||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
|
||||
const BUTTON_HIT_SLOP = { top: 4, right: 4, bottom: 4, left: 4 };
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listContainer: {
|
||||
height: EMOJI_BUTTON_SIZE,
|
||||
margin: 8,
|
||||
flexGrow: 1
|
||||
},
|
||||
container: {
|
||||
borderTopWidth: 1
|
||||
},
|
||||
searchContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: 12,
|
||||
marginBottom: 12
|
||||
},
|
||||
backButton: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: 4
|
||||
},
|
||||
emptyContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
emptyText: {
|
||||
...sharedStyles.textRegular,
|
||||
fontSize: 16
|
||||
},
|
||||
inputContainer: {
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
|
||||
interface IEmojiSearchBarProps {
|
||||
openEmoji: () => void;
|
||||
closeEmoji: () => void;
|
||||
onEmojiSelected: (emoji: IEmoji) => void;
|
||||
}
|
||||
|
||||
const EmojiSearchBar = ({ openEmoji, closeEmoji, onEmojiSelected }: IEmojiSearchBarProps): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const { frequentlyUsed } = useFrequentlyUsedEmoji(true);
|
||||
const [emojis, setEmojis] = useState<IEmoji[]>([]);
|
||||
|
||||
const handleTextChange = useDebounce(async (text: string) => {
|
||||
logEvent(events.MB_SB_EMOJI_SEARCH);
|
||||
setSearchText(text);
|
||||
const result = await searchEmojis(text);
|
||||
setEmojis(result);
|
||||
}, 300);
|
||||
|
||||
const handleEmojiSelected = (emoji: IEmoji) => {
|
||||
logEvent(events.MB_SB_EMOJI_SELECTED);
|
||||
onEmojiSelected(emoji);
|
||||
addFrequentlyUsed(emoji);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }: { item: IEmoji }) => <PressableEmoji emoji={item} onPress={handleEmojiSelected} />;
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { borderTopColor: colors.borderColor, backgroundColor: colors.messageboxBackground }]}>
|
||||
<FlatList
|
||||
horizontal
|
||||
data={searchText ? emojis : frequentlyUsed}
|
||||
renderItem={renderItem}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
ListEmptyComponent={() => (
|
||||
<View style={styles.emptyContainer} testID='no-results-found'>
|
||||
<Text style={[styles.emptyText, { color: colors.auxiliaryText }]}>{I18n.t('No_results_found')}</Text>
|
||||
</View>
|
||||
)}
|
||||
keyExtractor={item => (typeof item === 'string' ? item : item.name)}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
keyboardShouldPersistTaps='always'
|
||||
/>
|
||||
<View style={styles.searchContainer}>
|
||||
<Pressable
|
||||
style={({ pressed }: { pressed: boolean }) => [styles.backButton, { opacity: pressed ? 0.7 : 1 }]}
|
||||
onPress={openEmoji}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
testID='openback-emoji-keyboard'
|
||||
>
|
||||
<CustomIcon name='chevron-left' size={24} color={colors.auxiliaryTintColor} />
|
||||
</Pressable>
|
||||
<View style={styles.inputContainer}>
|
||||
<EmojiSearch onBlur={closeEmoji} onChangeText={handleTextChange} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmojiSearchBar;
|
|
@ -1,10 +1,9 @@
|
|||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
|
||||
import { IEmoji } from '../../../definitions/IEmoji';
|
||||
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
|
||||
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
||||
import MessageboxContext from '../Context';
|
||||
import styles from '../styles';
|
||||
|
||||
interface IMessageBoxMentionEmoji {
|
||||
|
@ -12,13 +11,10 @@ interface IMessageBoxMentionEmoji {
|
|||
}
|
||||
|
||||
const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => {
|
||||
const context = useContext(MessageboxContext);
|
||||
const { baseUrl } = context;
|
||||
|
||||
if (item.name) {
|
||||
return <CustomEmoji style={styles.mentionItemCustomEmoji} emoji={item} baseUrl={baseUrl} />;
|
||||
}
|
||||
if (typeof item === 'string') {
|
||||
return <Text style={styles.mentionItemEmoji}>{shortnameToUnicode(`:${item}:`)}</Text>;
|
||||
}
|
||||
return <CustomEmoji style={styles.mentionItemCustomEmoji} emoji={item} />;
|
||||
};
|
||||
|
||||
export default MentionEmoji;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useContext } from 'react';
|
||||
import { Text, TouchableOpacity } from 'react-native';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { themes } from '../../../lib/constants';
|
||||
import { IEmoji } from '../../../definitions/IEmoji';
|
||||
|
@ -37,7 +37,9 @@ const MentionItemContent = React.memo(({ trackingType, item }: IMessageBoxMentio
|
|||
case MENTIONS_TRACKING_TYPE_COMMANDS:
|
||||
return (
|
||||
<>
|
||||
<Text style={[styles.slash, { backgroundColor: themes[theme].borderColor, color: themes[theme].tintColor }]}>/</Text>
|
||||
<View style={[styles.slash, { backgroundColor: themes[theme].borderColor }]}>
|
||||
<Text style={{ color: themes[theme].tintColor }}>/</Text>
|
||||
</View>
|
||||
<Text style={[styles.mentionText, { color: themes[theme].titleText }]}>{item.id}</Text>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -29,7 +29,8 @@ const styles = StyleSheet.create({
|
|||
},
|
||||
username: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textMedium
|
||||
...sharedStyles.textMedium,
|
||||
flexShrink: 1
|
||||
},
|
||||
time: {
|
||||
fontSize: 12,
|
||||
|
@ -67,7 +68,7 @@ const ReplyPreview = React.memo(
|
|||
<View style={[styles.container, { backgroundColor: themes[theme].messageboxBackground }]}>
|
||||
<View style={[styles.messageContainer, { backgroundColor: themes[theme].chatComponentBackground }]}>
|
||||
<View style={styles.header}>
|
||||
<Text style={[styles.username, { color: themes[theme].tintColor }]}>
|
||||
<Text numberOfLines={1} style={[styles.username, { color: themes[theme].tintColor }]}>
|
||||
{useRealName ? message.u?.name : message.u?.username}
|
||||
</Text>
|
||||
<Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
||||
|
|
|
@ -4,5 +4,6 @@ export const MENTIONS_TRACKING_TYPE_COMMANDS = '/';
|
|||
export const MENTIONS_TRACKING_TYPE_ROOMS = '#';
|
||||
export const MENTIONS_TRACKING_TYPE_CANNED = '!';
|
||||
export const MENTIONS_COUNT_TO_DISPLAY = 4;
|
||||
export const MAX_EMOJIS_TO_DISPLAY = 20;
|
||||
|
||||
export const TIMEOUT_CLOSE_EMOJI = 300;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Alert, Keyboard, NativeModules, Text, View } from 'react-native';
|
||||
import { Alert, Keyboard, NativeModules, Text, View, BackHandler } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
|
||||
import ImagePicker, { Image, ImageOrVideo, Options } from 'react-native-image-crop-picker';
|
||||
|
@ -13,12 +13,11 @@ import { TextInput, IThemedTextInput } from '../TextInput';
|
|||
import { userTyping as userTypingAction } from '../../actions/room';
|
||||
import styles from './styles';
|
||||
import database from '../../lib/database';
|
||||
import { emojis } from '../EmojiPicker/emojis';
|
||||
import log, { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
import RecordAudio from './RecordAudio';
|
||||
import I18n from '../../i18n';
|
||||
import ReplyPreview from './ReplyPreview';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { themes, emojis } from '../../lib/constants';
|
||||
import LeftButtons from './LeftButtons';
|
||||
import RightButtons from './RightButtons';
|
||||
import { canUploadFile } from '../../lib/methods/helpers/media';
|
||||
|
@ -51,7 +50,8 @@ import {
|
|||
TGetCustomEmoji,
|
||||
TSubscriptionModel,
|
||||
TThreadModel,
|
||||
IMessage
|
||||
IMessage,
|
||||
IEmoji
|
||||
} from '../../definitions';
|
||||
import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types';
|
||||
import { getPermalinkMessage, search, sendFileMessage } from '../../lib/methods';
|
||||
|
@ -59,6 +59,9 @@ import { hasPermission, debounce, isAndroid, isIOS, isTablet, compareServerVersi
|
|||
import { Services } from '../../lib/services';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { ChatsStackParamList } from '../../stacks/types';
|
||||
import { EventTypes } from '../EmojiPicker/interfaces';
|
||||
import EmojiSearchbar from './EmojiSearchbar';
|
||||
import shortnameToUnicode from '../../lib/methods/helpers/shortnameToUnicode';
|
||||
|
||||
require('./EmojiKeyboard');
|
||||
|
||||
|
@ -129,6 +132,7 @@ interface IMessageBoxState {
|
|||
tshow: boolean;
|
||||
mentionLoading: boolean;
|
||||
permissionToUpload: boolean;
|
||||
showEmojiSearchbar: boolean;
|
||||
}
|
||||
|
||||
class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||
|
@ -183,7 +187,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
command: {},
|
||||
tshow: this.sendThreadToChannel,
|
||||
mentionLoading: false,
|
||||
permissionToUpload: true
|
||||
permissionToUpload: true,
|
||||
showEmojiSearchbar: false
|
||||
};
|
||||
this.text = '';
|
||||
this.selection = { start: 0, end: 0 };
|
||||
|
@ -209,6 +214,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
...videoPickerConfig,
|
||||
...libPickerLabels
|
||||
};
|
||||
|
||||
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
||||
get sendThreadToChannel() {
|
||||
|
@ -326,7 +333,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
tshow,
|
||||
mentionLoading,
|
||||
trackingType,
|
||||
permissionToUpload
|
||||
permissionToUpload,
|
||||
showEmojiSearchbar
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
|
@ -346,6 +354,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
if (nextState.showEmojiKeyboard !== showEmojiKeyboard) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.showEmojiSearchbar !== showEmojiSearchbar) {
|
||||
return true;
|
||||
}
|
||||
if (!isFocused()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -438,6 +449,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
if (isTablet) {
|
||||
EventEmiter.removeListener(KEY_COMMAND, this.handleCommands);
|
||||
}
|
||||
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
|
||||
}
|
||||
|
||||
setOptions = async () => {
|
||||
|
@ -519,7 +531,10 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
}, 100);
|
||||
|
||||
onKeyboardResigned = () => {
|
||||
const { showEmojiSearchbar } = this.state;
|
||||
if (!showEmojiSearchbar) {
|
||||
this.closeEmoji();
|
||||
}
|
||||
};
|
||||
|
||||
onPressMention = (item: any) => {
|
||||
|
@ -577,18 +592,50 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
}
|
||||
};
|
||||
|
||||
onEmojiSelected = (keyboardId: string, params: { emoji: string }) => {
|
||||
onKeyboardItemSelected = (keyboardId: string, params: { eventType: EventTypes; emoji: IEmoji }) => {
|
||||
const { eventType, emoji } = params;
|
||||
const { text } = this;
|
||||
const { emoji } = params;
|
||||
let newText = '';
|
||||
|
||||
// if messagebox has an active cursor
|
||||
const { start, end } = this.selection;
|
||||
const cursor = Math.max(start, end);
|
||||
newText = `${text.substr(0, cursor)}${emoji}${text.substr(cursor)}`;
|
||||
const newCursor = cursor + emoji.length;
|
||||
let newCursor;
|
||||
|
||||
switch (eventType) {
|
||||
case EventTypes.BACKSPACE_PRESSED:
|
||||
logEvent(events.MB_BACKSPACE);
|
||||
const emojiRegex = /\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]/;
|
||||
let charsToRemove = 1;
|
||||
const lastEmoji = text.substr(cursor > 0 ? cursor - 2 : text.length - 2, cursor > 0 ? cursor : text.length);
|
||||
// Check if last character is an emoji
|
||||
if (emojiRegex.test(lastEmoji)) charsToRemove = 2;
|
||||
newText =
|
||||
text.substr(0, (cursor > 0 ? cursor : text.length) - charsToRemove) + text.substr(cursor > 0 ? cursor : text.length);
|
||||
newCursor = cursor - charsToRemove;
|
||||
this.setInput(newText, { start: newCursor, end: newCursor });
|
||||
this.setShowSend(newText !== '');
|
||||
break;
|
||||
case EventTypes.EMOJI_PRESSED:
|
||||
logEvent(events.MB_EMOJI_SELECTED);
|
||||
let emojiText = '';
|
||||
if (typeof emoji === 'string') {
|
||||
const shortname = `:${emoji}:`;
|
||||
emojiText = shortnameToUnicode(shortname);
|
||||
} else {
|
||||
emojiText = `:${emoji.name}:`;
|
||||
}
|
||||
newText = `${text.substr(0, cursor)}${emojiText}${text.substr(cursor)}`;
|
||||
newCursor = cursor + emojiText.length;
|
||||
this.setInput(newText, { start: newCursor, end: newCursor });
|
||||
this.setShowSend(true);
|
||||
break;
|
||||
case EventTypes.SEARCH_PRESSED:
|
||||
logEvent(events.MB_EMOJI_SEARCH_PRESSED);
|
||||
this.setState({ showEmojiKeyboard: false, showEmojiSearchbar: true });
|
||||
break;
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
getPermalink = async (message: any) => {
|
||||
|
@ -611,7 +658,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
};
|
||||
|
||||
getUsers = debounce(async (keyword: any) => {
|
||||
let res = await search({ text: keyword, filterRooms: false, filterUsers: true });
|
||||
const { rid } = this.props;
|
||||
let res = await search({ text: keyword, filterRooms: false, filterUsers: true, rid });
|
||||
res = [...this.getFixedMentions(keyword), ...res];
|
||||
this.setState({ mentions: res, mentionLoading: false });
|
||||
}, 300);
|
||||
|
@ -621,16 +669,20 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
this.setState({ mentions: res, mentionLoading: false });
|
||||
}, 300);
|
||||
|
||||
getEmojis = debounce(async (keyword: any) => {
|
||||
const db = database.active;
|
||||
const customEmojisCollection = db.get('custom_emojis');
|
||||
getCustomEmojis = async (keyword: any, count: number) => {
|
||||
const likeString = sanitizeLikeString(keyword);
|
||||
const whereClause = [];
|
||||
if (likeString) {
|
||||
whereClause.push(Q.where('name', Q.like(`${likeString}%`)));
|
||||
}
|
||||
let customEmojis = await customEmojisCollection.query(...whereClause).fetch();
|
||||
customEmojis = customEmojis.slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||
const db = database.active;
|
||||
const customEmojisCollection = db.get('custom_emojis');
|
||||
const customEmojis = await (await customEmojisCollection.query(...whereClause).fetch()).slice(0, count);
|
||||
return customEmojis;
|
||||
};
|
||||
|
||||
getEmojis = debounce(async (keyword: any) => {
|
||||
const customEmojis = await this.getCustomEmojis(keyword, MENTIONS_COUNT_TO_DISPLAY);
|
||||
const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||
const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||
this.setState({ mentions: mergedEmojis || [], mentionLoading: false });
|
||||
|
@ -881,7 +933,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
|
||||
openEmoji = () => {
|
||||
logEvent(events.ROOM_OPEN_EMOJI);
|
||||
this.setState({ showEmojiKeyboard: true });
|
||||
this.setState({ showEmojiKeyboard: true, showEmojiSearchbar: false });
|
||||
this.stopTrackingMention();
|
||||
};
|
||||
|
||||
recordingCallback = (recording: any) => {
|
||||
|
@ -903,7 +956,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
};
|
||||
|
||||
closeEmoji = () => {
|
||||
this.setState({ showEmojiKeyboard: false });
|
||||
this.setState({ showEmojiKeyboard: false, showEmojiSearchbar: false });
|
||||
};
|
||||
|
||||
closeEmojiKeyboardAndFocus = () => {
|
||||
logEvent(events.ROOM_CLOSE_EMOJI);
|
||||
this.closeEmoji();
|
||||
this.focus();
|
||||
};
|
||||
|
||||
closeEmojiAndAction = (action?: Function, params?: any) => {
|
||||
|
@ -926,7 +985,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
|
||||
this.clearInput();
|
||||
this.debouncedOnChangeText.stop();
|
||||
this.closeEmoji();
|
||||
this.closeEmojiKeyboardAndFocus();
|
||||
this.stopTrackingMention();
|
||||
this.handleTyping(false);
|
||||
if (message.trim() === '' && !showSend) {
|
||||
|
@ -1083,10 +1142,34 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
);
|
||||
};
|
||||
|
||||
renderEmojiSearchbar = () => {
|
||||
const { showEmojiSearchbar } = this.state;
|
||||
|
||||
return showEmojiSearchbar ? (
|
||||
<EmojiSearchbar
|
||||
openEmoji={this.openEmoji}
|
||||
closeEmoji={this.closeEmoji}
|
||||
onEmojiSelected={(emoji: IEmoji) => {
|
||||
this.onKeyboardItemSelected('EmojiKeyboard', { eventType: EventTypes.EMOJI_PRESSED, emoji });
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
handleBackPress = () => {
|
||||
const { showEmojiSearchbar } = this.state;
|
||||
if (showEmojiSearchbar) {
|
||||
this.setState({ showEmojiSearchbar: false });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
renderContent = () => {
|
||||
const {
|
||||
recording,
|
||||
showEmojiKeyboard,
|
||||
showEmojiSearchbar,
|
||||
showSend,
|
||||
mentions,
|
||||
trackingType,
|
||||
|
@ -1149,11 +1232,11 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
const textInputAndButtons = !recording ? (
|
||||
<>
|
||||
<LeftButtons
|
||||
showEmojiKeyboard={showEmojiKeyboard}
|
||||
showEmojiKeyboard={showEmojiKeyboard || showEmojiSearchbar}
|
||||
editing={editing}
|
||||
editCancel={this.editCancel}
|
||||
openEmoji={this.openEmoji}
|
||||
closeEmoji={this.closeEmoji}
|
||||
closeEmoji={this.closeEmojiKeyboardAndFocus}
|
||||
/>
|
||||
<TextInput
|
||||
ref={component => (this.component = component)}
|
||||
|
@ -1197,6 +1280,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
{recordAudio}
|
||||
</View>
|
||||
{this.renderSendToChannel()}
|
||||
{this.renderEmojiSearchbar()}
|
||||
</View>
|
||||
{children}
|
||||
</>
|
||||
|
@ -1224,7 +1308,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null}
|
||||
kbInitialProps={{ theme }}
|
||||
onKeyboardResigned={this.onKeyboardResigned}
|
||||
onItemSelected={this.onEmojiSelected}
|
||||
onItemSelected={this.onKeyboardItemSelected}
|
||||
trackInteractive
|
||||
requiresSameParentToManageScrollView
|
||||
addBottomView
|
||||
|
|
|
@ -105,7 +105,7 @@ export default StyleSheet.create({
|
|||
},
|
||||
emojiKeyboardContainer: {
|
||||
flex: 1,
|
||||
borderTopWidth: StyleSheet.hairlineWidth
|
||||
borderTopWidth: 1
|
||||
},
|
||||
slash: {
|
||||
height: 30,
|
||||
|
@ -113,7 +113,7 @@ export default StyleSheet.create({
|
|||
padding: 5,
|
||||
paddingHorizontal: 12,
|
||||
marginHorizontal: 10,
|
||||
borderRadius: 2
|
||||
borderRadius: 4
|
||||
},
|
||||
commandPreviewImage: {
|
||||
justifyContent: 'center',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { Text, StyleProp, ViewStyle } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
import { themes } from '../../../lib/constants';
|
||||
|
@ -12,16 +12,17 @@ interface IPasscodeButton {
|
|||
icon?: TIconsName;
|
||||
disabled?: boolean;
|
||||
onPress?: Function;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const Button = React.memo(({ text, disabled, onPress, icon }: IPasscodeButton) => {
|
||||
const Button = React.memo(({ style, text, disabled, onPress, icon }: IPasscodeButton) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const press = () => onPress && onPress(text);
|
||||
|
||||
return (
|
||||
<Touch
|
||||
style={[styles.buttonView, { backgroundColor: 'transparent' }]}
|
||||
style={[styles.buttonView, { backgroundColor: 'transparent' }, style]}
|
||||
underlayColor={themes[theme].passcodeButtonActive}
|
||||
rippleColor={themes[theme].passcodeButtonActive}
|
||||
enabled={!disabled}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { Col, Grid, Row } from 'react-native-easy-grid';
|
||||
import range from 'lodash/range';
|
||||
import { View } from 'react-native';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import Orientation from 'react-native-orientation-locker';
|
||||
|
||||
import styles from './styles';
|
||||
import Button from './Button';
|
||||
|
@ -14,6 +15,8 @@ import { useTheme } from '../../../theme';
|
|||
import LockIcon from './LockIcon';
|
||||
import Title from './Title';
|
||||
import Subtitle from './Subtitle';
|
||||
import { useDimensions } from '../../../dimensions';
|
||||
import { isTablet } from '../../../lib/methods/helpers';
|
||||
|
||||
interface IPasscodeBase {
|
||||
type: string;
|
||||
|
@ -34,7 +37,25 @@ export interface IBase {
|
|||
|
||||
const Base = forwardRef<IBase, IPasscodeBase>(
|
||||
({ type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }, ref) => {
|
||||
useLayoutEffect(() => {
|
||||
if (!isTablet) {
|
||||
Orientation.lockToPortrait();
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (!isTablet) {
|
||||
Orientation.unlockAllOrientations();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { theme } = useTheme();
|
||||
const { height } = useDimensions();
|
||||
|
||||
// 206 is the height of the header calculating the margins, icon size height, title font size and subtitle height.
|
||||
// 56 is a fixed number to decrease the height of button numbers.
|
||||
const dinamicHeight = (height - 206 - 56) / 4;
|
||||
const heightButtonRow = { height: dinamicHeight > 102 ? 102 : dinamicHeight };
|
||||
|
||||
const rootRef = useRef<Animatable.View & View>(null);
|
||||
const dotsRef = useRef<Animatable.View & View>(null);
|
||||
|
@ -103,40 +124,40 @@ const Base = forwardRef<IBase, IPasscodeBase>(
|
|||
<Dots passcode={passcode} length={PASSCODE_LENGTH} />
|
||||
</Animatable.View>
|
||||
</Row>
|
||||
<Row style={[styles.row, styles.buttonRow]}>
|
||||
<Row style={[styles.row, heightButtonRow]}>
|
||||
{range(1, 4).map(i => (
|
||||
<Col key={i} style={styles.colButton}>
|
||||
<Button text={i.toString()} onPress={onPressNumber} />
|
||||
<Col key={i} style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Row style={[styles.row, styles.buttonRow]}>
|
||||
<Row style={[styles.row, heightButtonRow]}>
|
||||
{range(4, 7).map(i => (
|
||||
<Col key={i} style={styles.colButton}>
|
||||
<Button text={i.toString()} onPress={onPressNumber} />
|
||||
<Col key={i} style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Row style={[styles.row, styles.buttonRow]}>
|
||||
<Row style={[styles.row, heightButtonRow]}>
|
||||
{range(7, 10).map(i => (
|
||||
<Col key={i} style={styles.colButton}>
|
||||
<Button text={i.toString()} onPress={onPressNumber} />
|
||||
<Col key={i} style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} text={i.toString()} onPress={onPressNumber} />
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Row style={[styles.row, styles.buttonRow]}>
|
||||
<Row style={[styles.row, heightButtonRow]}>
|
||||
{showBiometry ? (
|
||||
<Col style={styles.colButton}>
|
||||
<Button icon='fingerprint' onPress={onBiometryPress} />
|
||||
<Col style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} icon='fingerprint' onPress={onBiometryPress} />
|
||||
</Col>
|
||||
) : (
|
||||
<Col style={styles.colButton} />
|
||||
<Col style={[styles.colButton, heightButtonRow]} />
|
||||
)}
|
||||
<Col style={styles.colButton}>
|
||||
<Button text='0' onPress={onPressNumber} />
|
||||
<Col style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} text='0' onPress={onPressNumber} />
|
||||
</Col>
|
||||
<Col style={styles.colButton}>
|
||||
<Button icon='backspace' onPress={onPressDelete} />
|
||||
<Col style={[styles.colButton, heightButtonRow]}>
|
||||
<Button style={heightButtonRow} icon='backspace' onPress={onPressDelete} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
|
|
|
@ -18,16 +18,12 @@ export default StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
buttonRow: {
|
||||
height: 102
|
||||
},
|
||||
colButton: {
|
||||
flex: 0,
|
||||
marginLeft: 12,
|
||||
marginRight: 12,
|
||||
alignItems: 'center',
|
||||
width: 78,
|
||||
height: 78
|
||||
width: 78
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 28,
|
||||
|
@ -37,7 +33,6 @@ export default StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 78,
|
||||
height: 78,
|
||||
borderRadius: 4
|
||||
},
|
||||
textTitle: {
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Text, Pressable, View, ScrollView } from 'react-native';
|
||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||
import { FlatList } from 'react-native-gesture-handler';
|
||||
|
||||
import Emoji from './message/Emoji';
|
||||
import { useTheme } from '../theme';
|
||||
import { TGetCustomEmoji } from '../definitions/IEmoji';
|
||||
import { IReaction } from '../definitions';
|
||||
import Avatar from './Avatar';
|
||||
import sharedStyles from '../views/Styles';
|
||||
|
||||
const MIN_TAB_WIDTH = 70;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
reactionsListContainer: { height: '100%', width: '100%' },
|
||||
tabBarItem: {
|
||||
paddingHorizontal: 10,
|
||||
paddingBottom: 10,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
reactionCount: { marginLeft: 5 },
|
||||
emojiName: { margin: 10 },
|
||||
userItemContainer: { marginHorizontal: 10, marginVertical: 5, flexDirection: 'row' },
|
||||
usernameContainer: { marginHorizontal: 10, justifyContent: 'center' },
|
||||
usernameText: { fontSize: 17, ...sharedStyles.textMedium },
|
||||
standardEmojiStyle: { fontSize: 20, color: '#fff' },
|
||||
customEmojiStyle: { width: 25, height: 25 }
|
||||
});
|
||||
|
||||
interface IReactionsListBase {
|
||||
baseUrl: string;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
}
|
||||
|
||||
interface IReactionsListProps extends IReactionsListBase {
|
||||
reactions?: IReaction[];
|
||||
width: number;
|
||||
}
|
||||
|
||||
interface ITabBarItem extends IReactionsListBase {
|
||||
tab: IReaction;
|
||||
index: number;
|
||||
goToPage?: (index: number) => void;
|
||||
}
|
||||
interface IReactionsTabBar extends IReactionsListBase {
|
||||
activeTab?: number;
|
||||
tabs?: IReaction[];
|
||||
goToPage?: (index: number) => void;
|
||||
width: number;
|
||||
}
|
||||
|
||||
const TabBarItem = ({ tab, index, goToPage, baseUrl, getCustomEmoji }: ITabBarItem) => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<Pressable
|
||||
key={tab.emoji}
|
||||
onPress={() => {
|
||||
goToPage?.(index);
|
||||
}}
|
||||
style={({ pressed }: { pressed: boolean }) => ({
|
||||
opacity: pressed ? 0.7 : 1
|
||||
})}
|
||||
>
|
||||
<View style={styles.tabBarItem}>
|
||||
<Emoji
|
||||
content={tab.emoji}
|
||||
standardEmojiStyle={styles.standardEmojiStyle}
|
||||
customEmojiStyle={styles.customEmojiStyle}
|
||||
baseUrl={baseUrl}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
/>
|
||||
<Text style={[styles.reactionCount, { color: colors.auxiliaryTintColor }]}>{tab.usernames.length}</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const ReactionsTabBar = ({ tabs, activeTab, goToPage, baseUrl, getCustomEmoji, width }: IReactionsTabBar) => {
|
||||
const tabWidth = tabs && Math.max(width / tabs.length, MIN_TAB_WIDTH);
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<View>
|
||||
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
|
||||
{tabs?.map((tab, index) => {
|
||||
const isActiveTab = activeTab === index;
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: tabWidth,
|
||||
borderBottomWidth: isActiveTab ? 2 : 1,
|
||||
borderColor: isActiveTab ? colors.tintActive : colors.separatorColor
|
||||
}}
|
||||
>
|
||||
<TabBarItem tab={tab} index={index} goToPage={goToPage} baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} />
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const UsersList = ({ tabLabel }: { tabLabel: IReaction }) => {
|
||||
const { colors } = useTheme();
|
||||
const { emoji, usernames } = tabLabel;
|
||||
return (
|
||||
<FlatList
|
||||
data={usernames}
|
||||
ListHeaderComponent={() => (
|
||||
<View style={styles.emojiName}>
|
||||
<Text style={{ color: colors.auxiliaryTintColor }}>{emoji}</Text>
|
||||
</View>
|
||||
)}
|
||||
renderItem={({ item }) => (
|
||||
<View style={styles.userItemContainer}>
|
||||
<Avatar text={item} size={36} />
|
||||
<View style={styles.usernameContainer}>
|
||||
<Text style={[styles.usernameText, { color: colors.titleText }]}>{item}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
keyExtractor={item => item}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ReactionsList = ({ reactions, baseUrl, getCustomEmoji, width }: IReactionsListProps): React.ReactElement => {
|
||||
// sorting reactions in descending order on the basic of number of users reacted
|
||||
const sortedReactions = reactions?.sort((reaction1, reaction2) => reaction2.usernames.length - reaction1.usernames.length);
|
||||
|
||||
return (
|
||||
<View style={styles.reactionsListContainer}>
|
||||
<ScrollableTabView renderTabBar={() => <ReactionsTabBar baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} width={width} />}>
|
||||
{sortedReactions?.map(reaction => (
|
||||
<UsersList tabLabel={reaction} key={reaction.emoji} />
|
||||
))}
|
||||
</ScrollableTabView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReactionsList;
|
|
@ -0,0 +1,75 @@
|
|||
import React from 'react';
|
||||
import { Text, View, FlatList } from 'react-native';
|
||||
|
||||
import Emoji from '../message/Emoji';
|
||||
import { useTheme } from '../../theme';
|
||||
import { IReaction } from '../../definitions';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import I18n from '../../i18n';
|
||||
import styles from './styles';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
interface IAllReactionsListItemProps {
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
item: IReaction;
|
||||
}
|
||||
|
||||
interface IAllTabProps {
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
tabLabel: IReaction;
|
||||
reactions?: IReaction[];
|
||||
}
|
||||
|
||||
const AllReactionsListItem = ({ item, getCustomEmoji }: IAllReactionsListItemProps) => {
|
||||
const { colors } = useTheme();
|
||||
const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name);
|
||||
const username = useAppSelector(state => state.login.user.username);
|
||||
const count = item.usernames.length;
|
||||
|
||||
let displayNames;
|
||||
if (useRealName && item.names) {
|
||||
displayNames = item.names
|
||||
.slice(0, 3)
|
||||
.map((name, index) => (item.usernames[index] === username ? I18n.t('you') : name))
|
||||
.join(', ');
|
||||
} else {
|
||||
displayNames = item.usernames
|
||||
.slice(0, 3)
|
||||
.map((otherUsername: string) => (username === otherUsername ? I18n.t('you') : otherUsername))
|
||||
.join(', ');
|
||||
}
|
||||
if (count > 3) {
|
||||
displayNames = `${displayNames} ${I18n.t('and_more')} ${count - 3}`;
|
||||
} else {
|
||||
displayNames = displayNames.replace(/,(?=[^,]*$)/, ` ${I18n.t('and')}`);
|
||||
}
|
||||
return (
|
||||
<View style={styles.listItemContainer}>
|
||||
<Emoji
|
||||
content={item.emoji}
|
||||
standardEmojiStyle={styles.allTabStandardEmojiStyle}
|
||||
customEmojiStyle={styles.allTabCustomEmojiStyle}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
/>
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={[styles.allListNPeopleReacted, { color: colors.bodyText }]}>
|
||||
{count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })}
|
||||
</Text>
|
||||
<Text style={[styles.allListWhoReacted, { color: colors.auxiliaryText }]}>{displayNames}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const AllTab = ({ reactions, getCustomEmoji }: IAllTabProps): React.ReactElement => (
|
||||
<View style={styles.allTabContainer} testID='reactionsListAllTab'>
|
||||
<FlatList
|
||||
data={reactions}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
renderItem={({ item }) => <AllReactionsListItem item={item} getCustomEmoji={getCustomEmoji} />}
|
||||
keyExtractor={item => item.emoji}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
export default AllTab;
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { TGetCustomEmoji, ICustomEmoji } from '../../definitions';
|
||||
import ReactionsList from '.';
|
||||
import { mockedStore as store } from '../../reducers/mockedStore';
|
||||
import { updateSettings } from '../../actions/settings';
|
||||
|
||||
const getCustomEmoji: TGetCustomEmoji = content => {
|
||||
const customEmoji = {
|
||||
marioparty: { name: content, extension: 'gif' },
|
||||
react_rocket: { name: content, extension: 'png' },
|
||||
nyan_rocket: { name: content, extension: 'png' }
|
||||
}[content] as ICustomEmoji;
|
||||
return customEmoji;
|
||||
};
|
||||
|
||||
const reactions = [
|
||||
{
|
||||
emoji: ':marioparty:',
|
||||
_id: 'marioparty',
|
||||
usernames: ['rocket.cat', 'diego.mello'],
|
||||
names: ['Rocket Cat', 'Diego Mello']
|
||||
},
|
||||
{
|
||||
emoji: ':react_rocket:',
|
||||
_id: 'react_rocket',
|
||||
usernames: ['rocket.cat', 'diego.mello'],
|
||||
names: ['Rocket Cat', 'Diego Mello']
|
||||
},
|
||||
{
|
||||
emoji: ':nyan_rocket:',
|
||||
_id: 'nyan_rocket',
|
||||
usernames: ['rocket.cat'],
|
||||
names: ['Rocket Cat']
|
||||
},
|
||||
{
|
||||
emoji: ':grinning:',
|
||||
_id: 'grinning',
|
||||
usernames: ['diego.mello'],
|
||||
names: ['Diego Mello']
|
||||
},
|
||||
{
|
||||
emoji: ':tada:',
|
||||
_id: 'tada',
|
||||
usernames: ['diego.mello'],
|
||||
names: ['Diego Mello']
|
||||
}
|
||||
];
|
||||
|
||||
export const ReactionsListStory = () => {
|
||||
store.dispatch(updateSettings('UI_Use_Real_Name', false));
|
||||
return (
|
||||
<View style={{ paddingVertical: 10, flex: 1 }}>
|
||||
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReactionsListFullName = () => {
|
||||
store.dispatch(updateSettings('UI_Use_Real_Name', true));
|
||||
return (
|
||||
<View style={{ paddingVertical: 10, flex: 1 }}>
|
||||
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'ReactionsList'
|
||||
};
|
|
@ -0,0 +1,79 @@
|
|||
import React from 'react';
|
||||
import { fireEvent, render, within } from '@testing-library/react-native';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import ReactionsList from '.';
|
||||
import { mockedStore } from '../../reducers/mockedStore';
|
||||
|
||||
const getCustomEmoji = jest.fn();
|
||||
const reactions = [
|
||||
{
|
||||
emoji: 'marioparty',
|
||||
_id: 'marioparty',
|
||||
usernames: ['rocket.cat', 'diego.mello'],
|
||||
names: ['Rocket Cat', 'Diego Mello']
|
||||
},
|
||||
{
|
||||
emoji: 'react_rocket',
|
||||
_id: 'react_rocket',
|
||||
usernames: ['rocket.cat', 'diego.mello'],
|
||||
names: ['Rocket Cat', 'Diego Mello']
|
||||
},
|
||||
{
|
||||
emoji: 'nyan_rocket',
|
||||
_id: 'nyan_rocket',
|
||||
usernames: ['rocket.cat'],
|
||||
names: ['Rocket Cat']
|
||||
},
|
||||
{
|
||||
emoji: 'grinning',
|
||||
_id: 'grinning',
|
||||
usernames: ['diego.mello'],
|
||||
names: ['Diego Mello']
|
||||
}
|
||||
];
|
||||
|
||||
const Render = () => (
|
||||
<Provider store={mockedStore}>
|
||||
<ReactionsList getCustomEmoji={getCustomEmoji} reactions={reactions} />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
describe('ReactionsList', () => {
|
||||
test('should render Reactions List', async () => {
|
||||
const { findByTestId } = render(<Render />);
|
||||
const ReactionsListView = await findByTestId('reactionsList');
|
||||
expect(ReactionsListView).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should render tab bar', async () => {
|
||||
const { findByTestId } = render(<Render />);
|
||||
const AllTab = await findByTestId('reactionsTabBar');
|
||||
expect(AllTab).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should render All tab', async () => {
|
||||
const { findByTestId } = render(<Render />);
|
||||
const AllTab = await findByTestId('reactionsListAllTab');
|
||||
expect(AllTab).toBeTruthy();
|
||||
});
|
||||
|
||||
test('correct tab on clicking tab item', async () => {
|
||||
const { findByTestId } = render(<Render />);
|
||||
const tab = await findByTestId(`tabBarItem-${reactions[0].emoji}`);
|
||||
fireEvent.press(tab);
|
||||
const usersList = await findByTestId(`usersList-${reactions[0].emoji}`);
|
||||
expect(usersList).toBeTruthy();
|
||||
const emojiName = await within(usersList).getByTestId(`usersListEmojiName`);
|
||||
expect(emojiName.props.children).toEqual(reactions[0].emoji);
|
||||
});
|
||||
|
||||
test('should render correct number of reactions', async () => {
|
||||
const { findByTestId } = render(<Render />);
|
||||
const tab = await findByTestId(`tabBarItem-${reactions[0].emoji}`);
|
||||
fireEvent.press(tab);
|
||||
const usersList = await findByTestId(`usersList-${reactions[0].emoji}`);
|
||||
const allReactions = await within(usersList).getAllByTestId('userItem');
|
||||
expect(allReactions).toHaveLength(reactions[0].usernames.length);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,86 @@
|
|||
import React from 'react';
|
||||
import { Text, Pressable, View, ScrollView } from 'react-native';
|
||||
|
||||
import Emoji from '../message/Emoji';
|
||||
import { useTheme } from '../../theme';
|
||||
import { IReaction } from '../../definitions';
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import I18n from '../../i18n';
|
||||
import styles, { MIN_TAB_WIDTH } from './styles';
|
||||
import { useDimensions, useOrientation } from '../../dimensions';
|
||||
|
||||
interface ITabBarItem {
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
tab: IReaction;
|
||||
index: number;
|
||||
goToPage?: (index: number) => void;
|
||||
}
|
||||
interface IReactionsTabBar {
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
activeTab?: number;
|
||||
tabs?: IReaction[];
|
||||
goToPage?: (index: number) => void;
|
||||
}
|
||||
|
||||
const TabBarItem = ({ tab, index, goToPage, getCustomEmoji }: ITabBarItem) => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<Pressable
|
||||
key={tab.emoji}
|
||||
onPress={() => {
|
||||
goToPage?.(index);
|
||||
}}
|
||||
style={({ pressed }: { pressed: boolean }) => ({
|
||||
opacity: pressed ? 0.7 : 1
|
||||
})}
|
||||
testID={`tabBarItem-${tab.emoji}`}
|
||||
>
|
||||
<View style={styles.tabBarItem}>
|
||||
{tab._id === 'All' ? (
|
||||
<Text style={[styles.allTabItem, { color: colors.auxiliaryTintColor }]}>{I18n.t('All')}</Text>
|
||||
) : (
|
||||
<>
|
||||
<Emoji
|
||||
content={tab.emoji}
|
||||
standardEmojiStyle={styles.standardEmojiStyle}
|
||||
customEmojiStyle={styles.customEmojiStyle}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
/>
|
||||
<Text style={[styles.reactionCount, { color: colors.auxiliaryTintColor }]}>{tab.usernames.length}</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const ReactionsTabBar = ({ tabs, activeTab, goToPage, getCustomEmoji }: IReactionsTabBar): React.ReactElement => {
|
||||
const { isLandscape } = useOrientation();
|
||||
const { width } = useDimensions();
|
||||
const reactionsListWidth = isLandscape ? width / 2 : width;
|
||||
const tabWidth = tabs && Math.max(reactionsListWidth / tabs.length, MIN_TAB_WIDTH);
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<View testID='reactionsTabBar'>
|
||||
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
|
||||
{tabs?.map((tab, index) => {
|
||||
const isActiveTab = activeTab === index;
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: tabWidth,
|
||||
borderBottomWidth: isActiveTab ? 2 : 1,
|
||||
borderColor: isActiveTab ? colors.tintActive : colors.separatorColor
|
||||
}}
|
||||
key={tab.emoji}
|
||||
>
|
||||
<TabBarItem tab={tab} index={index} goToPage={goToPage} getCustomEmoji={getCustomEmoji} />
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReactionsTabBar;
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react';
|
||||
import { Text, View, FlatList } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import { IReaction, IApplicationState } from '../../definitions';
|
||||
import Avatar from '../Avatar';
|
||||
import styles from './styles';
|
||||
|
||||
const UsersList = ({ tabLabel }: { tabLabel: IReaction }): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
const useRealName = useSelector((state: IApplicationState) => state.settings.UI_Use_Real_Name);
|
||||
|
||||
const { emoji, usernames, names } = tabLabel;
|
||||
const users =
|
||||
names?.length > 0
|
||||
? usernames.map((username, index) => ({ username, name: names[index] }))
|
||||
: usernames.map(username => ({ username, name: '' }));
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={users}
|
||||
contentContainerStyle={styles.listContainer}
|
||||
ListHeaderComponent={
|
||||
<View style={styles.emojiNameContainer}>
|
||||
<Text style={[styles.emojiName, { color: colors.auxiliaryText }]} testID='usersListEmojiName'>
|
||||
{emoji}
|
||||
</Text>
|
||||
</View>
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<View style={styles.listItemContainer} testID='userItem'>
|
||||
<Avatar text={item.username} size={36} />
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={[styles.usernameText, { color: colors.bodyText }]} numberOfLines={1}>
|
||||
{useRealName && item.name ? item.name : item.username}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
keyExtractor={item => item.username}
|
||||
testID={`usersList-${emoji}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsersList;
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||
|
||||
import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
||||
import { IReaction } from '../../definitions';
|
||||
import I18n from '../../i18n';
|
||||
import styles from './styles';
|
||||
import AllTab from './AllTab';
|
||||
import UsersList from './UsersList';
|
||||
import ReactionsTabBar from './ReactionsTabBar';
|
||||
|
||||
interface IReactionsListProps {
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
reactions?: IReaction[];
|
||||
}
|
||||
|
||||
const ReactionsList = ({ reactions, getCustomEmoji }: IReactionsListProps): React.ReactElement => {
|
||||
// sorting reactions in descending order on the basic of number of users reacted
|
||||
const sortedReactions = reactions?.sort((reaction1, reaction2) => reaction2.usernames.length - reaction1.usernames.length);
|
||||
const allTabLabel = { emoji: I18n.t('All'), usernames: [], names: [], _id: 'All' };
|
||||
return (
|
||||
<View style={styles.container} testID='reactionsList'>
|
||||
<ScrollableTabView renderTabBar={() => <ReactionsTabBar getCustomEmoji={getCustomEmoji} />}>
|
||||
<AllTab tabLabel={allTabLabel} reactions={sortedReactions} getCustomEmoji={getCustomEmoji} />
|
||||
{sortedReactions?.map(reaction => (
|
||||
<UsersList tabLabel={reaction} key={reaction.emoji} />
|
||||
))}
|
||||
</ScrollableTabView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReactionsList;
|
|
@ -0,0 +1,88 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import sharedStyles from '../../views/Styles';
|
||||
|
||||
export const MIN_TAB_WIDTH = 70;
|
||||
|
||||
export default StyleSheet.create({
|
||||
container: {
|
||||
flex: 1
|
||||
},
|
||||
allTabContainer: {
|
||||
flex: 1
|
||||
},
|
||||
tabBarItem: {
|
||||
paddingBottom: 4,
|
||||
height: 44,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
listContainer: {
|
||||
marginHorizontal: 12,
|
||||
marginVertical: 8,
|
||||
paddingBottom: 30
|
||||
},
|
||||
reactionCount: {
|
||||
marginLeft: 4,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
emojiNameContainer: {
|
||||
marginVertical: 8
|
||||
},
|
||||
emojiName: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textMedium
|
||||
},
|
||||
listItemContainer: {
|
||||
marginVertical: 6,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
textContainer: {
|
||||
flex: 1,
|
||||
marginLeft: 8,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
usernameText: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
standardEmojiStyle: {
|
||||
fontSize: 20,
|
||||
width: 24,
|
||||
height: 24,
|
||||
textAlign: 'center',
|
||||
color: '#fff'
|
||||
},
|
||||
customEmojiStyle: {
|
||||
width: 24,
|
||||
height: 24
|
||||
},
|
||||
allTabItem: {
|
||||
fontSize: 16,
|
||||
...sharedStyles.textSemibold
|
||||
},
|
||||
allTabStandardEmojiStyle: {
|
||||
fontSize: 30,
|
||||
width: 36,
|
||||
textAlign: 'center',
|
||||
color: '#fff'
|
||||
},
|
||||
allTabCustomEmojiStyle: {
|
||||
width: 36,
|
||||
height: 36
|
||||
},
|
||||
allListItemContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
allListNPeopleReacted: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textMedium
|
||||
},
|
||||
allListWhoReacted: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textRegular
|
||||
}
|
||||
});
|
|
@ -7,6 +7,7 @@ import { MarkdownPreview } from '../markdown';
|
|||
import RoomTypeIcon from '../RoomTypeIcon';
|
||||
import { TUserStatus, IOmnichannelSource } from '../../definitions';
|
||||
import { useTheme } from '../../theme';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
const HIT_SLOP = {
|
||||
top: 5,
|
||||
|
@ -141,8 +142,9 @@ const Header = React.memo(
|
|||
const { colors } = useTheme();
|
||||
const portrait = height > width;
|
||||
let scale = 1;
|
||||
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
|
||||
|
||||
if (!portrait && !tmid) {
|
||||
if (!portrait && !tmid && !isMasterDetail) {
|
||||
if (usersTyping.length > 0 || subtitle) {
|
||||
scale = 0.8;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { StyleSheet, TextInputProps, View } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import I18n from '../../i18n';
|
||||
import { FormTextInput } from '../TextInput';
|
||||
|
||||
|
@ -14,13 +15,15 @@ const styles = StyleSheet.create({
|
|||
const SearchBox = ({ onChangeText, onSubmitEditing, testID }: TextInputProps): JSX.Element => {
|
||||
const [text, setText] = useState('');
|
||||
|
||||
const { colors } = useTheme();
|
||||
|
||||
const internalOnChangeText = useCallback(value => {
|
||||
setText(value);
|
||||
onChangeText?.(value);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View testID='searchbox'>
|
||||
<View testID='searchbox' style={{ backgroundColor: colors.backgroundColor }}>
|
||||
<FormTextInput
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
|
|
|
@ -26,9 +26,10 @@ const styles = StyleSheet.create({
|
|||
...sharedStyles.textRegular,
|
||||
height: 48,
|
||||
fontSize: 16,
|
||||
padding: 14,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderRadius: 2
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4
|
||||
},
|
||||
inputIconLeft: {
|
||||
paddingLeft: 45
|
||||
|
@ -37,17 +38,17 @@ const styles = StyleSheet.create({
|
|||
paddingRight: 45
|
||||
},
|
||||
wrap: {
|
||||
position: 'relative'
|
||||
position: 'relative',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
iconContainer: {
|
||||
position: 'absolute',
|
||||
top: 14
|
||||
position: 'absolute'
|
||||
},
|
||||
iconLeft: {
|
||||
left: 15
|
||||
left: 12
|
||||
},
|
||||
iconRight: {
|
||||
right: 15
|
||||
right: 12
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -98,7 +99,7 @@ export const FormTextInput = ({
|
|||
style={[
|
||||
styles.input,
|
||||
iconLeft && styles.inputIconLeft,
|
||||
(secureTextEntry || iconRight) && styles.inputIconRight,
|
||||
(secureTextEntry || iconRight || showClearInput) && styles.inputIconRight,
|
||||
{
|
||||
backgroundColor: colors.backgroundColor,
|
||||
borderColor: colors.separatorColor,
|
||||
|
|
|
@ -27,3 +27,44 @@ export const ShortAndLong = () => (
|
|||
</View>
|
||||
</>
|
||||
);
|
||||
export const Icons = () => (
|
||||
<>
|
||||
<View style={styles.paddingHorizontal}>
|
||||
<FormTextInput label='Right icon' placeholder='placeholder' value={item.name} iconRight={'close'} />
|
||||
<FormTextInput label='Left icon' placeholder='placeholder' value={item.longText} iconLeft={'mail'} />
|
||||
<FormTextInput label='Both icons' placeholder='placeholder' value={item.longText} iconLeft={'mail'} iconRight={'add'} />
|
||||
<FormTextInput
|
||||
label='Icon and touchable clear input'
|
||||
placeholder='placeholder'
|
||||
value={item.longText}
|
||||
onClearInput={() => {}}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
|
||||
export const Multiline = () => (
|
||||
<>
|
||||
<View style={styles.paddingHorizontal}>
|
||||
<FormTextInput label='Multiline text' placeholder='placeholder' multiline value={`${item.name}\n\n${item.longText}\n`} />
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
|
||||
export const SecureTextEntry = () => (
|
||||
<>
|
||||
<View style={styles.paddingHorizontal}>
|
||||
<FormTextInput label='Secure text disabled' placeholder='placeholder' value={item.name} />
|
||||
<FormTextInput label='Secure text enabled' placeholder='placeholder' value={item.name} secureTextEntry />
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
|
||||
export const Loading = () => (
|
||||
<>
|
||||
<View style={styles.paddingHorizontal}>
|
||||
<FormTextInput label='Loading false' placeholder='placeholder' value={item.name} loading={false} />
|
||||
<FormTextInput label='Loading true' placeholder='placeholder' value={item.name} loading />
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import { StyleSheet, Text, unstable_batchedUpdates, View } from 'react-native';
|
||||
import DateTimePicker, { Event } from '@react-native-community/datetimepicker';
|
||||
import Touchable from 'react-native-platform-touchable';
|
||||
import { BlockContext } from '@rocket.chat/ui-kit';
|
||||
|
@ -19,8 +19,8 @@ const styles = StyleSheet.create({
|
|||
input: {
|
||||
height: 48,
|
||||
paddingLeft: 16,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderRadius: 2,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
@ -48,12 +48,16 @@ export const DatePicker = ({ element, language, action, context, loading, value,
|
|||
// timestamp as number exists in Event
|
||||
// @ts-ignore
|
||||
const onChange = ({ nativeEvent: { timestamp } }: Event, date?: Date) => {
|
||||
if (date || timestamp) {
|
||||
const newDate = date || new Date(timestamp);
|
||||
unstable_batchedUpdates(() => {
|
||||
onChangeDate(newDate);
|
||||
action({ value: moment(newDate).format('YYYY-MM-DD') });
|
||||
if (isAndroid) {
|
||||
onShow(false);
|
||||
}
|
||||
});
|
||||
action({ value: moment(newDate).format('YYYY-MM-DD') });
|
||||
}
|
||||
};
|
||||
|
||||
let button = placeholder ? <Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} /> : null;
|
||||
|
@ -85,7 +89,13 @@ export const DatePicker = ({ element, language, action, context, loading, value,
|
|||
}
|
||||
|
||||
const content = show ? (
|
||||
<DateTimePicker mode='date' display='default' value={currentDate} onChange={onChange} textColor={themes[theme].titleText} />
|
||||
<DateTimePicker
|
||||
mode='date'
|
||||
display={isAndroid ? 'default' : 'inline'}
|
||||
value={currentDate}
|
||||
onChange={onChange}
|
||||
textColor={themes[theme].titleText}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
|
|
|
@ -22,11 +22,11 @@ const Input = ({ children, onPress, loading, inputStyle, placeholder, disabled,
|
|||
return (
|
||||
<Touchable
|
||||
onPress={onPress}
|
||||
style={[{ backgroundColor: colors.backgroundColor }, inputStyle]}
|
||||
style={[{ backgroundColor: colors.backgroundColor }, styles.inputBorder, inputStyle]}
|
||||
background={Touchable.Ripple(colors.bannerBackground)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<View style={[styles.input, { borderColor: colors.separatorColor }, innerInputStyle]}>
|
||||
<View style={[styles.input, styles.inputBorder, { borderColor: colors.separatorColor }, innerInputStyle]}>
|
||||
{placeholder ? <Text style={[styles.pickerText, { color: colors.auxiliaryText }]}>{placeholder}</Text> : children}
|
||||
{loading ? (
|
||||
<ActivityIndicator style={styles.icon} />
|
||||
|
|
|
@ -31,12 +31,14 @@ export default StyleSheet.create({
|
|||
flexDirection: 'row',
|
||||
flex: 1
|
||||
},
|
||||
inputBorder: {
|
||||
borderRadius: 4
|
||||
},
|
||||
input: {
|
||||
minHeight: 48,
|
||||
paddingHorizontal: 8,
|
||||
paddingBottom: 0,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderRadius: 2,
|
||||
borderWidth: 1,
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
|
|
@ -19,8 +19,8 @@ const styles = StyleSheet.create({
|
|||
viewContainer: {
|
||||
marginBottom: 16,
|
||||
paddingHorizontal: 16,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderRadius: 2,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
pickerText: {
|
||||
|
|
|
@ -10,33 +10,24 @@ interface IEmoji {
|
|||
literal: string;
|
||||
isMessageContainsOnlyEmoji: boolean;
|
||||
getCustomEmoji?: Function;
|
||||
baseUrl: string;
|
||||
customEmojis?: any;
|
||||
style?: object;
|
||||
onEmojiSelected?: Function;
|
||||
tabEmojiStyle?: object;
|
||||
}
|
||||
|
||||
const Emoji = React.memo(
|
||||
({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis = true, style = {} }: IEmoji) => {
|
||||
const Emoji = React.memo(({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, customEmojis = true, style = {} }: IEmoji) => {
|
||||
const { colors } = useTheme();
|
||||
const emojiUnicode = shortnameToUnicode(literal);
|
||||
const emoji: any = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, ''));
|
||||
if (emoji && customEmojis) {
|
||||
return (
|
||||
<CustomEmoji
|
||||
baseUrl={baseUrl}
|
||||
style={[isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji, style]}
|
||||
emoji={emoji}
|
||||
/>
|
||||
);
|
||||
return <CustomEmoji style={[isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji, style]} emoji={emoji} />;
|
||||
}
|
||||
return (
|
||||
<Text style={[{ color: colors.bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}>
|
||||
{emojiUnicode}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export default Emoji;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { NavigationContainer } from '@react-navigation/native';
|
|||
|
||||
import Markdown, { MarkdownPreview } from '.';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TGetCustomEmoji, IEmoji } from '../../definitions/IEmoji';
|
||||
import { TGetCustomEmoji, ICustomEmoji } from '../../definitions/IEmoji';
|
||||
|
||||
const theme = 'light';
|
||||
|
||||
|
@ -16,7 +16,6 @@ const styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
const baseUrl = 'https://open.rocket.chat';
|
||||
const longText =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
|
||||
const lineBreakText = `a
|
||||
|
@ -34,7 +33,7 @@ const getCustomEmoji: TGetCustomEmoji = content => {
|
|||
marioparty: { name: content, extension: 'gif' },
|
||||
react_rocket: { name: content, extension: 'png' },
|
||||
nyan_rocket: { name: content, extension: 'png' }
|
||||
}[content] as IEmoji;
|
||||
}[content] as ICustomEmoji;
|
||||
return customEmoji;
|
||||
};
|
||||
|
||||
|
@ -108,13 +107,8 @@ export const Emoji = () => (
|
|||
<View style={styles.container}>
|
||||
<Markdown msg='Unicode: 😃😇👍' theme={theme} />
|
||||
<Markdown msg='Shortnames: :joy::+1:' theme={theme} />
|
||||
<Markdown
|
||||
msg='Custom emojis: :react_rocket: :nyan_rocket: :marioparty:'
|
||||
theme={theme}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
baseUrl={baseUrl}
|
||||
/>
|
||||
<Markdown msg='😃 :+1: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
|
||||
<Markdown msg='Custom emojis: :react_rocket: :nyan_rocket: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} />
|
||||
<Markdown msg='😃 :+1: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ interface IMarkdownProps {
|
|||
md?: MarkdownAST;
|
||||
mentions?: IUserMention[];
|
||||
getCustomEmoji?: TGetCustomEmoji;
|
||||
baseUrl?: string;
|
||||
username?: string;
|
||||
tmid?: string;
|
||||
numberOfLines?: number;
|
||||
|
@ -46,6 +45,7 @@ interface IMarkdownProps {
|
|||
testID?: string;
|
||||
style?: StyleProp<TextStyle>[];
|
||||
onLinkPress?: TOnLinkPress;
|
||||
isTranslated?: boolean;
|
||||
}
|
||||
|
||||
type TLiteral = {
|
||||
|
@ -94,10 +94,8 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
|
||||
constructor(props: IMarkdownProps) {
|
||||
super(props);
|
||||
if (!this.isNewMarkdown) {
|
||||
this.renderer = this.createRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
createRenderer = () =>
|
||||
new Renderer({
|
||||
|
@ -235,13 +233,12 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
};
|
||||
|
||||
renderEmoji = ({ literal }: TLiteral) => {
|
||||
const { getCustomEmoji, baseUrl = '', customEmojis, style } = this.props;
|
||||
const { getCustomEmoji, customEmojis, style } = this.props;
|
||||
return (
|
||||
<MarkdownEmoji
|
||||
literal={literal}
|
||||
isMessageContainsOnlyEmoji={this.isMessageContainsOnlyEmoji}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
baseUrl={baseUrl}
|
||||
customEmojis={customEmojis}
|
||||
style={style}
|
||||
/>
|
||||
|
@ -321,19 +318,18 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
useRealName,
|
||||
username = '',
|
||||
getCustomEmoji,
|
||||
baseUrl = '',
|
||||
onLinkPress
|
||||
onLinkPress,
|
||||
isTranslated
|
||||
} = this.props;
|
||||
|
||||
if (!msg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.isNewMarkdown) {
|
||||
if (this.isNewMarkdown && !isTranslated) {
|
||||
return (
|
||||
<NewMarkdown
|
||||
username={username}
|
||||
baseUrl={baseUrl}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
useRealName={useRealName}
|
||||
tokens={md}
|
||||
|
@ -350,7 +346,7 @@ class Markdown extends PureComponent<IMarkdownProps, any> {
|
|||
let ast = parser.parse(m);
|
||||
ast = mergeTextNodes(ast);
|
||||
this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
|
||||
return this.renderer.render(ast);
|
||||
return this.renderer?.render(ast) || null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import { Text } from 'react-native';
|
|||
import { Emoji as EmojiProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import shortnameToUnicode from '../../../lib/methods/helpers/shortnameToUnicode';
|
||||
import { themes } from '../../../lib/constants';
|
||||
import { useTheme } from '../../../theme';
|
||||
import styles from '../styles';
|
||||
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
|
||||
|
@ -15,21 +14,21 @@ interface IEmojiProps {
|
|||
}
|
||||
|
||||
const Emoji = ({ block, isBigEmoji }: IEmojiProps) => {
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, getCustomEmoji } = useContext(MarkdownContext);
|
||||
const { colors } = useTheme();
|
||||
const { getCustomEmoji } = useContext(MarkdownContext);
|
||||
|
||||
if ('unicode' in block) {
|
||||
return <Text style={[{ color: themes[theme].bodyText }, isBigEmoji ? styles.textBig : styles.text]}>{block.unicode}</Text>;
|
||||
return <Text style={[{ color: colors.bodyText }, isBigEmoji ? styles.textBig : styles.text]}>{block.unicode}</Text>;
|
||||
}
|
||||
const emojiToken = block?.shortCode ? `:${block.shortCode}:` : `:${block.value?.value}:`;
|
||||
const emojiUnicode = shortnameToUnicode(emojiToken);
|
||||
const emoji = getCustomEmoji?.(block.value?.value);
|
||||
|
||||
if (emoji) {
|
||||
return <CustomEmoji baseUrl={baseUrl} style={[isBigEmoji ? styles.customEmojiBig : styles.customEmoji]} emoji={emoji} />;
|
||||
return <CustomEmoji style={[isBigEmoji ? styles.customEmojiBig : styles.customEmoji]} emoji={emoji} />;
|
||||
}
|
||||
return (
|
||||
<Text style={[{ color: themes[theme].bodyText }, isBigEmoji && emojiToken !== emojiUnicode ? styles.textBig : styles.text]}>
|
||||
<Text style={[{ color: colors.bodyText }, isBigEmoji && emojiToken !== emojiUnicode ? styles.textBig : styles.text]}>
|
||||
{emojiUnicode}
|
||||
</Text>
|
||||
);
|
||||
|
|
|
@ -41,18 +41,21 @@ const Link = ({ value }: ILinkProps) => {
|
|||
return (
|
||||
<Text onPress={handlePress} onLongPress={onLongPress} style={[styles.link, { color: themes[theme].actionTintColor }]}>
|
||||
{(block => {
|
||||
switch (block.type) {
|
||||
const blockArray = Array.isArray(block) ? block : [block];
|
||||
return blockArray.map(blockInArray => {
|
||||
switch (blockInArray.type) {
|
||||
case 'PLAIN_TEXT':
|
||||
return block.value;
|
||||
return blockInArray.value;
|
||||
case 'STRIKE':
|
||||
return <Strike value={block.value} />;
|
||||
return <Strike value={blockInArray.value} />;
|
||||
case 'ITALIC':
|
||||
return <Italic value={block.value} />;
|
||||
return <Italic value={blockInArray.value} />;
|
||||
case 'BOLD':
|
||||
return <Bold value={block.value} />;
|
||||
return <Bold value={blockInArray.value} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
})(label)}
|
||||
</Text>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,6 @@ interface IMarkdownContext {
|
|||
channels?: IUserChannel[];
|
||||
useRealName?: boolean;
|
||||
username?: string;
|
||||
baseUrl?: string;
|
||||
navToRoomInfo?: Function;
|
||||
getCustomEmoji?: Function;
|
||||
onLinkPress?: Function;
|
||||
|
@ -18,7 +17,6 @@ const defaultState = {
|
|||
channels: [],
|
||||
useRealName: false,
|
||||
username: '',
|
||||
baseUrl: '',
|
||||
navToRoomInfo: () => {}
|
||||
};
|
||||
|
||||
|
|
|
@ -3,22 +3,25 @@ import { StyleSheet, View } from 'react-native';
|
|||
import { NavigationContainer } from '@react-navigation/native';
|
||||
|
||||
import NewMarkdownComponent from '.';
|
||||
import { themes } from '../../../lib/constants';
|
||||
import { colors, themes } from '../../../lib/constants';
|
||||
import { longText } from '../../../../.storybook/utils';
|
||||
import { ThemeContext } from '../../../theme';
|
||||
|
||||
const theme = 'light';
|
||||
|
||||
export default {
|
||||
title: 'NewMarkdown',
|
||||
decorators: [
|
||||
(Story: any) => (
|
||||
<NavigationContainer>
|
||||
<ThemeContext.Provider value={{ theme, colors: colors[theme] }}>
|
||||
<Story />
|
||||
</ThemeContext.Provider>
|
||||
</NavigationContainer>
|
||||
)
|
||||
]
|
||||
};
|
||||
|
||||
const theme = 'light';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginHorizontal: 15,
|
||||
|
@ -34,11 +37,8 @@ const getCustomEmoji = (content: string) => {
|
|||
}[content];
|
||||
return customEmoji;
|
||||
};
|
||||
const baseUrl = 'https://open.rocket.chat';
|
||||
|
||||
const NewMarkdown = ({ ...props }) => (
|
||||
<NewMarkdownComponent baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} username='rocket.cat' {...props} />
|
||||
);
|
||||
const NewMarkdown = ({ ...props }) => <NewMarkdownComponent getCustomEmoji={getCustomEmoji} username='rocket.cat' {...props} />;
|
||||
|
||||
const simpleTextMsg = [
|
||||
{
|
||||
|
@ -340,7 +340,7 @@ const emojiTokens = [
|
|||
export const Emoji = () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={bigEmojiTokens} />
|
||||
<NewMarkdown tokens={emojiTokens} getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} />
|
||||
<NewMarkdown tokens={emojiTokens} getCustomEmoji={getCustomEmoji} />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
@ -423,10 +423,73 @@ const markdownLink = [
|
|||
}
|
||||
];
|
||||
|
||||
const markdownLinkWithEmphasis = [
|
||||
{
|
||||
type: 'PARAGRAPH',
|
||||
value: [
|
||||
{
|
||||
type: 'LINK',
|
||||
value: {
|
||||
src: {
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'https://rocket.chat/'
|
||||
},
|
||||
label: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'Normal Link - '
|
||||
},
|
||||
{
|
||||
type: 'BOLD',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'Bold'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: ' '
|
||||
},
|
||||
{
|
||||
type: 'STRIKE',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'strike'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: ' and '
|
||||
},
|
||||
{
|
||||
type: 'ITALIC',
|
||||
value: [
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: 'Italic'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'PLAIN_TEXT',
|
||||
value: ' Styles'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const Links = () => (
|
||||
<View style={styles.container}>
|
||||
<NewMarkdown tokens={rocketChatLink} />
|
||||
<NewMarkdown tokens={markdownLink} />
|
||||
<NewMarkdown tokens={markdownLinkWithEmphasis} />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
|
|
@ -3,20 +3,15 @@ import { Text } from 'react-native';
|
|||
import { Plain as PlainProps } from '@rocket.chat/message-parser';
|
||||
|
||||
import styles from '../styles';
|
||||
import { useTheme } from '../../../theme';
|
||||
import { themes } from '../../../lib/constants';
|
||||
|
||||
interface IPlainProps {
|
||||
value: PlainProps['value'];
|
||||
}
|
||||
|
||||
const Plain = ({ value }: IPlainProps) => {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<Text accessibilityLabel={value} style={[styles.plainText, { color: themes[theme].bodyText }]}>
|
||||
const Plain = ({ value }: IPlainProps) => (
|
||||
<Text accessibilityLabel={value} style={styles.plainText}>
|
||||
{value}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
export default Plain;
|
||||
|
|
|
@ -24,7 +24,6 @@ interface IBodyProps {
|
|||
navToRoomInfo?: Function;
|
||||
useRealName?: boolean;
|
||||
username: string;
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
const Body = ({
|
||||
|
@ -35,7 +34,6 @@ const Body = ({
|
|||
username,
|
||||
navToRoomInfo,
|
||||
getCustomEmoji,
|
||||
baseUrl,
|
||||
onLinkPress
|
||||
}: IBodyProps): React.ReactElement | null => {
|
||||
if (isEmpty(tokens)) {
|
||||
|
@ -51,7 +49,6 @@ const Body = ({
|
|||
username,
|
||||
navToRoomInfo,
|
||||
getCustomEmoji,
|
||||
baseUrl,
|
||||
onLinkPress
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -57,7 +57,7 @@ const AttachedActions = ({ attachment }: { attachment: IAttachment }) => {
|
|||
};
|
||||
|
||||
const Attachments: React.FC<IMessageAttachments> = React.memo(
|
||||
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply }: IMessageAttachments) => {
|
||||
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply, id }: IMessageAttachments) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
if (!attachments || attachments.length === 0) {
|
||||
|
@ -80,7 +80,15 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
|
|||
|
||||
if (file && file.audio_url) {
|
||||
return (
|
||||
<Audio key={file.audio_url} file={file} getCustomEmoji={getCustomEmoji} isReply={isReply} style={style} theme={theme} />
|
||||
<Audio
|
||||
key={file.audio_url}
|
||||
file={file}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
isReply={isReply}
|
||||
style={style}
|
||||
theme={theme}
|
||||
messageId={id}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -106,7 +114,16 @@ const Attachments: React.FC<IMessageAttachments> = React.memo(
|
|||
);
|
||||
}
|
||||
|
||||
return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} />;
|
||||
return (
|
||||
<Reply
|
||||
key={index}
|
||||
index={index}
|
||||
attachment={file}
|
||||
timeFormat={timeFormat}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
messageId={id}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return <>{attachmentsElements}</>;
|
||||
},
|
||||
|
|
|
@ -20,6 +20,8 @@ import { TGetCustomEmoji } from '../../definitions/IEmoji';
|
|||
import { IAttachment } from '../../definitions';
|
||||
import { TSupportedThemes } from '../../theme';
|
||||
import { downloadAudioFile } from '../../lib/methods/audioFile';
|
||||
import EventEmitter from '../../lib/methods/helpers/events';
|
||||
import { PAUSE_AUDIO } from './constants';
|
||||
|
||||
interface IButton {
|
||||
loading: boolean;
|
||||
|
@ -36,6 +38,7 @@ interface IMessageAudioProps {
|
|||
theme: TSupportedThemes;
|
||||
getCustomEmoji: TGetCustomEmoji;
|
||||
scale?: number;
|
||||
messageId: string;
|
||||
}
|
||||
|
||||
interface IMessageAudioState {
|
||||
|
@ -127,8 +130,13 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
this.sound.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate);
|
||||
}
|
||||
|
||||
pauseSound = () => {
|
||||
EventEmitter.removeListener(PAUSE_AUDIO, this.pauseSound);
|
||||
this.togglePlayPause();
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
const { file } = this.props;
|
||||
const { file, messageId } = this.props;
|
||||
const { baseUrl, user } = this.context;
|
||||
|
||||
let url = file.audio_url;
|
||||
|
@ -139,7 +147,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
this.setState({ loading: true });
|
||||
try {
|
||||
if (url) {
|
||||
const audio = await downloadAudioFile(`${url}?rc_uid=${user.id}&rc_token=${user.token}`, url);
|
||||
const audio = await downloadAudioFile(`${url}?rc_uid=${user.id}&rc_token=${user.token}`, url, messageId);
|
||||
await this.sound.loadAsync({ uri: audio });
|
||||
}
|
||||
} catch {
|
||||
|
@ -182,6 +190,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
}
|
||||
|
||||
async componentWillUnmount() {
|
||||
EventEmitter.removeListener(PAUSE_AUDIO, this.pauseSound);
|
||||
try {
|
||||
await this.sound.stopAsync();
|
||||
} catch {
|
||||
|
@ -220,6 +229,7 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
try {
|
||||
await this.sound.stopAsync();
|
||||
this.setState({ paused: true, currentTime: 0 });
|
||||
EventEmitter.removeListener(PAUSE_AUDIO, this.pauseSound);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
|
@ -242,7 +252,10 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
try {
|
||||
if (paused) {
|
||||
await this.sound.pauseAsync();
|
||||
EventEmitter.removeListener(PAUSE_AUDIO, this.pauseSound);
|
||||
} else {
|
||||
EventEmitter.emit(PAUSE_AUDIO);
|
||||
EventEmitter.addEventListener(PAUSE_AUDIO, this.pauseSound);
|
||||
await Audio.setAudioModeAsync(mode);
|
||||
await this.sound.playAsync();
|
||||
}
|
||||
|
@ -282,7 +295,6 @@ class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioStat
|
|||
<Markdown
|
||||
msg={description}
|
||||
style={[isReply && style]}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
|
|
|
@ -30,7 +30,7 @@ export const Item = () => (
|
|||
user: { username: 'Marcos' }
|
||||
}}
|
||||
>
|
||||
<CollapsibleQuote key={0} index={0} attachment={testAttachment} getCustomEmoji={() => {}} timeFormat='LT' />
|
||||
<CollapsibleQuote key={0} index={0} attachment={testAttachment} getCustomEmoji={() => null} timeFormat='LT' />
|
||||
</MessageContext.Provider>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -82,7 +82,7 @@ interface IMessageReply {
|
|||
const Fields = React.memo(
|
||||
({ attachment, getCustomEmoji }: IMessageFields) => {
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const { user } = useContext(MessageContext);
|
||||
|
||||
if (!attachment.fields) {
|
||||
return null;
|
||||
|
@ -97,7 +97,6 @@ const Fields = React.memo(
|
|||
</Text>
|
||||
<Markdown
|
||||
msg={field?.value || ''}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { CustomIcon } from '../../../CustomIcon';
|
|||
import styles from '../../styles';
|
||||
import { useTheme } from '../../../../theme';
|
||||
|
||||
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread: boolean }) => {
|
||||
const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread }: { isReadReceiptEnabled?: boolean; unread?: boolean }) => {
|
||||
const { theme } = useTheme();
|
||||
if (isReadReceiptEnabled && !unread && unread !== null) {
|
||||
return <CustomIcon name='check' color={themes[theme].tintColor} size={16} style={styles.rightIcons} />;
|
||||
|
|
|
@ -17,8 +17,8 @@ interface IRightIcons {
|
|||
type: MessageType;
|
||||
msg?: string;
|
||||
isEdited: boolean;
|
||||
isReadReceiptEnabled: boolean;
|
||||
unread: boolean;
|
||||
isReadReceiptEnabled?: boolean;
|
||||
unread?: boolean;
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ const RightIcons = ({ type, msg, isEdited, hasError, isReadReceiptEnabled, unrea
|
|||
<Encrypted type={type} />
|
||||
<Edited testID={`${msg}-edited`} isEdited={isEdited} />
|
||||
<MessageError hasError={hasError} />
|
||||
<ReadReceipt isReadReceiptEnabled={isReadReceiptEnabled} unread={unread || false} />
|
||||
<ReadReceipt isReadReceiptEnabled={isReadReceiptEnabled} unread={unread} />
|
||||
</View>
|
||||
);
|
||||
|
||||
|
|
|
@ -6,16 +6,17 @@ import I18n from '../../i18n';
|
|||
import styles from './styles';
|
||||
import Markdown, { MarkdownPreview } from '../markdown';
|
||||
import User from './User';
|
||||
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME, getInfoMessage } from './utils';
|
||||
import { messageHaveAuthorName, getInfoMessage } from './utils';
|
||||
import MessageContext from './Context';
|
||||
import { IMessageContent } from './interfaces';
|
||||
import { useTheme } from '../../theme';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { MessageTypesValues } from '../../definitions';
|
||||
|
||||
const Content = React.memo(
|
||||
(props: IMessageContent) => {
|
||||
const { theme } = useTheme();
|
||||
const { baseUrl, user, onLinkPress } = useContext(MessageContext);
|
||||
const { user, onLinkPress } = useContext(MessageContext);
|
||||
|
||||
if (props.isInfo) {
|
||||
// @ts-ignore
|
||||
|
@ -26,8 +27,7 @@ const Content = React.memo(
|
|||
{infoMessage}
|
||||
</Text>
|
||||
);
|
||||
|
||||
if (SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME.includes(props.type)) {
|
||||
if (messageHaveAuthorName(props.type as MessageTypesValues)) {
|
||||
return (
|
||||
<Text>
|
||||
<User {...props} /> {renderMessageContent}
|
||||
|
@ -54,7 +54,6 @@ const Content = React.memo(
|
|||
<Markdown
|
||||
msg={props.msg}
|
||||
md={props.md}
|
||||
baseUrl={baseUrl}
|
||||
getCustomEmoji={props.getCustomEmoji}
|
||||
enableMessageParser={user.enableMessageParserEarlyAdoption}
|
||||
username={user.username}
|
||||
|
@ -65,6 +64,7 @@ const Content = React.memo(
|
|||
useRealName={props.useRealName}
|
||||
theme={theme}
|
||||
onLinkPress={onLinkPress}
|
||||
isTranslated={props.isTranslated}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ import CustomEmoji from '../EmojiPicker/CustomEmoji';
|
|||
import { IMessageEmoji } from './interfaces';
|
||||
|
||||
const Emoji = React.memo(
|
||||
({ content, baseUrl, standardEmojiStyle, customEmojiStyle, getCustomEmoji }: IMessageEmoji) => {
|
||||
({ content, standardEmojiStyle, customEmojiStyle, getCustomEmoji }: IMessageEmoji) => {
|
||||
const parsedContent = content.replace(/^:|:$/g, '');
|
||||
const emoji = getCustomEmoji(parsedContent);
|
||||
if (emoji) {
|
||||
return <CustomEmoji key={content} baseUrl={baseUrl} style={customEmojiStyle} emoji={emoji} />;
|
||||
return <CustomEmoji key={content} style={customEmojiStyle} emoji={emoji} />;
|
||||
}
|
||||
return <Text style={standardEmojiStyle}>{shortnameToUnicode(content)}</Text>;
|
||||
},
|
||||
|
|
|
@ -81,7 +81,6 @@ const ImageContainer = React.memo(
|
|||
<Markdown
|
||||
msg={file.description}
|
||||
style={[isReply && style]}
|
||||
baseUrl={baseUrl}
|
||||
username={user.username}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
theme={theme}
|
||||
|
|
|
@ -820,8 +820,8 @@ export const SystemMessages = () => (
|
|||
<Message msg='public' type='room_changed_privacy' isInfo />
|
||||
<Message type='room_e2e_disabled' isInfo />
|
||||
<Message type='room_e2e_enabled' isInfo />
|
||||
<Message type='removed-user-from-team' isInfo />
|
||||
<Message type='added-user-to-team' isInfo />
|
||||
<Message msg='rocket.cat' type='removed-user-from-team' isInfo />
|
||||
<Message msg='rocket.cat' type='added-user-to-team' isInfo />
|
||||
<Message type='user-added-room-to-team' isInfo msg='channel-name' />
|
||||
<Message type='user-converted-to-team' isInfo msg='channel-name' />
|
||||
<Message type='user-converted-to-channel' isInfo msg='channel-name' />
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue