From 647cd4c9c2f36cecae69bc3ac1968cda9b87d182 Mon Sep 17 00:00:00 2001 From: Julian Mondragon Date: Mon, 31 Oct 2022 17:25:52 -0500 Subject: [PATCH 1/6] MM-39720_Invite People - phase 1 --- app/components/slide_up_panel_item/index.tsx | 12 ++- app/database/migration/server/index.ts | 12 +++ app/database/models/server/team.ts | 3 + .../server_data_operator/transformers/team.ts | 1 + app/database/schema/server/index.ts | 2 +- .../schema/server/table_schemas/team.ts | 1 + app/database/schema/server/test.ts | 4 +- .../categories_list/header/header.test.tsx | 1 + .../categories_list/header/header.tsx | 13 +++- .../categories_list/header/index.ts | 18 ++++- .../header/plus_menu/index.tsx | 69 ++++++++++++++++- .../categories_list/header/plus_menu/item.tsx | 14 +++- assets/base/i18n/en.json | 3 + detox/e2e/support/ui/component/plus_menu.ts | 2 + detox/e2e/support/ui/screen/channel_list.ts | 1 + detox/e2e/test/teams/invite_people.e2e.ts | 76 +++++++++++++++++++ types/database/models/servers/team.ts | 3 + 17 files changed, 224 insertions(+), 11 deletions(-) create mode 100644 detox/e2e/test/teams/invite_people.e2e.ts diff --git a/app/components/slide_up_panel_item/index.tsx b/app/components/slide_up_panel_item/index.tsx index 840d8ec5e..143483c32 100644 --- a/app/components/slide_up_panel_item/index.tsx +++ b/app/components/slide_up_panel_item/index.tsx @@ -21,6 +21,7 @@ type SlideUpPanelProps = { textStyles?: TextStyle; testID?: string; text: string; + topDivider?: boolean; } export const ITEM_HEIGHT = 48; @@ -61,10 +62,15 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { color: theme.centerChannelColor, ...typography('Body', 200, 'Regular'), }, + divider: { + borderTopWidth: 1, + borderStyle: 'solid', + borderColor: changeOpacity(theme.centerChannelColor, 0.08), + }, }; }); -const SlideUpPanelItem = ({destructive, icon, imageStyles, onPress, testID, text, textStyles, rightIcon = false}: SlideUpPanelProps) => { +const SlideUpPanelItem = ({destructive, icon, imageStyles, onPress, testID, text, textStyles, rightIcon = false, topDivider}: SlideUpPanelProps) => { const theme = useTheme(); const handleOnPress = useCallback(preventDoubleTap(onPress, 500), []); const style = getStyleSheet(theme); @@ -99,10 +105,12 @@ const SlideUpPanelItem = ({destructive, icon, imageStyles, onPress, testID, text } } + const divider = topDivider ? style.divider : {}; + return ( diff --git a/app/database/migration/server/index.ts b/app/database/migration/server/index.ts index 11f715c9d..51ebc7666 100644 --- a/app/database/migration/server/index.ts +++ b/app/database/migration/server/index.ts @@ -11,10 +11,22 @@ import {MM_TABLES} from '@constants/database'; const {SERVER: { GROUP, MY_CHANNEL, + TEAM, THREAD, }} = MM_TABLES; export default schemaMigrations({migrations: [ + { + toVersion: 4, + steps: [ + addColumns({ + table: TEAM, + columns: [ + {name: 'invite_id', type: 'string'}, + ], + }), + ], + }, { toVersion: 3, steps: [ diff --git a/app/database/models/server/team.ts b/app/database/models/server/team.ts index 9d4dabc72..11efd6b60 100644 --- a/app/database/models/server/team.ts +++ b/app/database/models/server/team.ts @@ -87,6 +87,9 @@ export default class TeamModel extends Model implements TeamModelInterface { /** allowed_domains : List of domains that can join this team */ @field('allowed_domains') allowedDomains!: string; + /** invite_id : The token id to use in invites to the team */ + @field('invite_id') inviteId!: string; + /** categories : All the categories associated with this team */ @children(CATEGORY) categories!: Query; diff --git a/app/database/operator/server_data_operator/transformers/team.ts b/app/database/operator/server_data_operator/transformers/team.ts index f9945558a..24a6be3ef 100644 --- a/app/database/operator/server_data_operator/transformers/team.ts +++ b/app/database/operator/server_data_operator/transformers/team.ts @@ -72,6 +72,7 @@ export const transformTeamRecord = ({action, database, value}: TransformerArgs): team.allowedDomains = raw.allowed_domains; team.isGroupConstrained = Boolean(raw.group_constrained); team.lastTeamIconUpdatedAt = raw.last_team_icon_update; + team.inviteId = raw.invite_id; }; return prepareBaseRecord({ diff --git a/app/database/schema/server/index.ts b/app/database/schema/server/index.ts index 48c9a8d5a..f4466e0f4 100644 --- a/app/database/schema/server/index.ts +++ b/app/database/schema/server/index.ts @@ -37,7 +37,7 @@ import { } from './table_schemas'; export const serverSchema: AppSchema = appSchema({ - version: 3, + version: 4, tables: [ CategorySchema, CategoryChannelSchema, diff --git a/app/database/schema/server/table_schemas/team.ts b/app/database/schema/server/table_schemas/team.ts index 17a127b00..26ad4bf98 100644 --- a/app/database/schema/server/table_schemas/team.ts +++ b/app/database/schema/server/table_schemas/team.ts @@ -19,5 +19,6 @@ export default tableSchema({ {name: 'name', type: 'string'}, {name: 'type', type: 'string'}, {name: 'update_at', type: 'number'}, + {name: 'invite_id', type: 'string'}, ], }); diff --git a/app/database/schema/server/test.ts b/app/database/schema/server/test.ts index 0b6fd6e29..34ee07d84 100644 --- a/app/database/schema/server/test.ts +++ b/app/database/schema/server/test.ts @@ -43,7 +43,7 @@ const { describe('*** Test schema for SERVER database ***', () => { it('=> The SERVER SCHEMA should strictly match', () => { expect(serverSchema).toEqual({ - version: 3, + version: 4, tables: { [CATEGORY]: { name: CATEGORY, @@ -463,6 +463,7 @@ describe('*** Test schema for SERVER database ***', () => { name: {name: 'name', type: 'string'}, type: {name: 'type', type: 'string'}, update_at: {name: 'update_at', type: 'number'}, + invite_id: {name: 'invite_id', type: 'string'}, }, columnArray: [ {name: 'allowed_domains', type: 'string'}, @@ -474,6 +475,7 @@ describe('*** Test schema for SERVER database ***', () => { {name: 'name', type: 'string'}, {name: 'type', type: 'string'}, {name: 'update_at', type: 'number'}, + {name: 'invite_id', type: 'string'}, ], }, [TEAM_CHANNEL_HISTORY]: { diff --git a/app/screens/home/channel_list/categories_list/header/header.test.tsx b/app/screens/home/channel_list/categories_list/header/header.test.tsx index 2b425144e..2832e7f3d 100644 --- a/app/screens/home/channel_list/categories_list/header/header.test.tsx +++ b/app/screens/home/channel_list/categories_list/header/header.test.tsx @@ -15,6 +15,7 @@ describe('components/channel_list/header', () => { pushProxyStatus={PUSH_PROXY_STATUS_VERIFIED} canCreateChannels={true} canJoinChannels={true} + canInvitePeople={true} displayName={'Test!'} />, ); diff --git a/app/screens/home/channel_list/categories_list/header/header.tsx b/app/screens/home/channel_list/categories_list/header/header.tsx index befe2c357..c391deedc 100644 --- a/app/screens/home/channel_list/categories_list/header/header.tsx +++ b/app/screens/home/channel_list/categories_list/header/header.tsx @@ -28,7 +28,9 @@ import PlusMenu from './plus_menu'; type Props = { canCreateChannels: boolean; canJoinChannels: boolean; - displayName: string; + canInvitePeople: boolean; + displayName?: string; + inviteId?: string; iconPad?: boolean; onHeaderPress?: () => void; pushProxyStatus: string; @@ -92,7 +94,9 @@ const hitSlop: Insets = {top: 10, bottom: 30, left: 20, right: 20}; const ChannelListHeader = ({ canCreateChannels, canJoinChannels, + canInvitePeople, displayName, + inviteId, iconPad, onHeaderPress, pushProxyStatus, @@ -118,6 +122,9 @@ const ChannelListHeader = ({ ); }; @@ -132,6 +139,10 @@ const ChannelListHeader = ({ items += 1; } + if (canInvitePeople) { + items += 1; + } + bottomSheet({ closeButtonId, renderContent, diff --git a/app/screens/home/channel_list/categories_list/header/index.ts b/app/screens/home/channel_list/categories_list/header/index.ts index 82a25f25d..ef99d7958 100644 --- a/app/screens/home/channel_list/categories_list/header/index.ts +++ b/app/screens/home/channel_list/categories_list/header/index.ts @@ -8,7 +8,7 @@ import {switchMap} from 'rxjs/operators'; import {Permissions} from '@constants'; import {observePermissionForTeam} from '@queries/servers/role'; -import {observePushVerificationStatus} from '@queries/servers/system'; +import {observeConfig, observePushVerificationStatus} from '@queries/servers/system'; import {observeCurrentTeam} from '@queries/servers/team'; import {observeCurrentUser} from '@queries/servers/user'; @@ -21,6 +21,12 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { const currentUser = observeCurrentUser(database); + const config = observeConfig(database); + + const enableOpenServer = config.pipe( + switchMap((t) => of$(t?.EnableOpenServer === 'true')), + ); + const canJoinChannels = combineLatest([currentUser, team]).pipe( switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true)), ); @@ -37,12 +43,22 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { switchMap(([open, priv]) => of$(open || priv)), ); + const canAddUserToTeam = combineLatest([currentUser, team]).pipe( + switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.ADD_USER_TO_TEAM, false)), + ); + return { canCreateChannels, canJoinChannels, + canInvitePeople: combineLatest([enableOpenServer, canAddUserToTeam]).pipe( + switchMap(([openServer, addUser]) => of$(openServer && addUser)), + ), displayName: team.pipe( switchMap((t) => of$(t?.displayName)), ), + inviteId: team.pipe( + switchMap((t) => of$(t?.inviteId)), + ), pushProxyStatus: observePushVerificationStatus(database), }; }); diff --git a/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx b/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx index 40b854e74..35bc9b806 100644 --- a/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx +++ b/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx @@ -3,9 +3,13 @@ import React, {useCallback} from 'react'; import {useIntl} from 'react-intl'; +import {Platform} from 'react-native'; +import Share from 'react-native-share'; +import {ShareOptions} from 'react-native-share/lib/typescript/types'; import CompassIcon from '@components/compass_icon'; import {Screens} from '@constants'; +import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {dismissBottomSheet, showModal} from '@screens/navigation'; @@ -14,11 +18,15 @@ import PlusMenuItem from './item'; type Props = { canCreateChannels: boolean; canJoinChannels: boolean; + canInvitePeople: boolean; + displayName?: string; + inviteId?: string; } -const PlusMenuList = ({canCreateChannels, canJoinChannels}: Props) => { +const PlusMenuList = ({canCreateChannels, canJoinChannels, canInvitePeople, displayName, inviteId}: Props) => { const intl = useIntl(); const theme = useTheme(); + const serverUrl = useServerUrl(); const browseChannels = useCallback(async () => { await dismissBottomSheet(); @@ -48,6 +56,59 @@ const PlusMenuList = ({canCreateChannels, canJoinChannels}: Props) => { }); }, [intl, theme]); + const invitePeopleToTeam = async () => { + await dismissBottomSheet(); + + const url = `${serverUrl}/signup_user_complete/?id=${inviteId}`; + const title = intl.formatMessage({id: 'invite_people_to_team.title', defaultMessage: 'Join the {team} team'}, {team: displayName}); + const message = intl.formatMessage({id: 'invite_people_to_team.message', defaultMessage: 'Here´s a link to collaborate and communicate with us on Mattermost.'}); + const icon = 'data:/;base64,'; + + const options: ShareOptions = Platform.select({ + ios: { + activityItemSources: [ + { + placeholderItem: { + type: 'url', + content: url, + }, + item: { + default: { + type: 'text', + content: `${message} ${url}`, + }, + copyToPasteBoard: { + type: 'url', + content: url, + }, + }, + subject: { + default: title, + }, + linkMetadata: { + originalUrl: url, + url, + title, + icon, + }, + }, + ], + }, + default: { + title, + subject: title, + url, + showAppsToView: true, + }, + }); + + Share.open( + options, + ).catch(() => { + // do nothing + }); + }; + return ( <> {canJoinChannels && @@ -66,6 +127,12 @@ const PlusMenuList = ({canCreateChannels, canJoinChannels}: Props) => { pickerAction='openDirectMessage' onPress={openDirectMessage} /> + {canInvitePeople && + + } ); }; diff --git a/app/screens/home/channel_list/categories_list/header/plus_menu/item.tsx b/app/screens/home/channel_list/categories_list/header/plus_menu/item.tsx index e46b285eb..590fa77ae 100644 --- a/app/screens/home/channel_list/categories_list/header/plus_menu/item.tsx +++ b/app/screens/home/channel_list/categories_list/header/plus_menu/item.tsx @@ -7,7 +7,7 @@ import {useIntl} from 'react-intl'; import SlideUpPanelItem from '@components/slide_up_panel_item'; type PlusMenuItemProps = { - pickerAction: 'browseChannels' | 'createNewChannel' | 'openDirectMessage'; + pickerAction: 'browseChannels' | 'createNewChannel' | 'openDirectMessage' | 'invitePeopleToTeam'; onPress: () => void; }; @@ -32,14 +32,20 @@ const PlusMenuItem = ({pickerAction, onPress}: PlusMenuItemProps) => { text: intl.formatMessage({id: 'plus_menu.open_direct_message.title', defaultMessage: 'Open a Direct Message'}), testID: 'plus_menu_item.open_direct_message', }, + invitePeopleToTeam: { + icon: 'account-plus-outline', + text: intl.formatMessage({id: 'plus_menu.invite_people_to_team.title', defaultMessage: 'Invite people to the team'}), + testID: 'plus_menu_item.invite_people_to_team', + topDivider: true, + }, }; + const itemType = menuItems[pickerAction]; + return ( ); }; diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index de610b01a..a5b42d806 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -315,6 +315,8 @@ "intro.welcome": "Welcome to {displayName} channel.", "intro.welcome.private": "Only invited members can see messages posted in this private channel.", "intro.welcome.public": "Add some more team members to the channel or start a conversation below.", + "invite_people_to_team.message": "Here´s a link to collaborate and communicate with us on Mattermost.", + "invite_people_to_team.title": "Join the {team} team", "last_users_message.added_to_channel.type": "were **added to the channel** by {actor}.", "last_users_message.added_to_team.type": "were **added to the team** by {actor}.", "last_users_message.first": "{firstUser} and ", @@ -684,6 +686,7 @@ "pinned_messages.empty.title": "No pinned messages yet", "plus_menu.browse_channels.title": "Browse Channels", "plus_menu.create_new_channel.title": "Create New Channel", + "plus_menu.invite_people_to_team.title": "Invite people to the team", "plus_menu.open_direct_message.title": "Open a Direct Message", "post_body.check_for_out_of_channel_groups_mentions.message": "did not get notified by this mention because they are not in the channel. They are also not a member of the groups linked to this channel.", "post_body.check_for_out_of_channel_mentions.link.and": " and ", diff --git a/detox/e2e/support/ui/component/plus_menu.ts b/detox/e2e/support/ui/component/plus_menu.ts index 713e2d39c..ef23c34fa 100644 --- a/detox/e2e/support/ui/component/plus_menu.ts +++ b/detox/e2e/support/ui/component/plus_menu.ts @@ -6,11 +6,13 @@ class PlusMenu { browseChannelsItem: 'plus_menu_item.browse_channels', createNewChannelItem: 'plus_menu_item.create_new_channel', openDirectMessageItem: 'plus_menu_item.open_direct_message', + invitePeopleToTeamItem: 'plus_menu_item.invite_people_to_team', }; browseChannelsItem = element(by.id(this.testID.browseChannelsItem)); createNewChannelItem = element(by.id(this.testID.createNewChannelItem)); openDirectMessageItem = element(by.id(this.testID.openDirectMessageItem)); + invitePeopleToTeamItem = element(by.id(this.testID.invitePeopleToTeamItem)); } const plusMenu = new PlusMenu(); diff --git a/detox/e2e/support/ui/screen/channel_list.ts b/detox/e2e/support/ui/screen/channel_list.ts index 413555ae6..869950e25 100644 --- a/detox/e2e/support/ui/screen/channel_list.ts +++ b/detox/e2e/support/ui/screen/channel_list.ts @@ -39,6 +39,7 @@ class ChannelListScreen { browseChannelsItem = PlusMenu.browseChannelsItem; createNewChannelItem = PlusMenu.createNewChannelItem; openDirectMessageItem = PlusMenu.openDirectMessageItem; + invitePeopleToTeamItem = PlusMenu.invitePeopleToTeamItem; getCategoryCollapsed = (categoryKey: string) => { return element(by.id(`${this.testID.categoryHeaderPrefix}${categoryKey}.collapsed.true`)); diff --git a/detox/e2e/test/teams/invite_people.e2e.ts b/detox/e2e/test/teams/invite_people.e2e.ts new file mode 100644 index 000000000..6975899cc --- /dev/null +++ b/detox/e2e/test/teams/invite_people.e2e.ts @@ -0,0 +1,76 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import {Setup} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelListScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {isIos} from '@support/utils'; +import {expect} from 'detox'; + +function systemDialog(label: string) { + if (device.getPlatform() === 'ios') { + return element(by.label(label)).atIndex(0); + } + return element(by.text(label)); +} + +describe('Teams - Invite people', () => { + const serverOneDisplayName = 'Server 1'; + + let testTeam: any; + let testUser: any; + + beforeAll(async () => { + const {team, user} = await Setup.apiInit(siteOneUrl); + + testTeam = team; + testUser = user; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(testUser); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Close share dialog + await ChannelListScreen.headerTeamDisplayName.tap(); + + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4729_2 - should be able to share a URL invite to the team', async () => { + // # Open plus menu + await ChannelListScreen.headerPlusButton.tap(); + + // * Verify invite people to team item is available + await expect(ChannelListScreen.invitePeopleToTeamItem).toExist(); + + // # Tap on invite people to team item + await ChannelListScreen.invitePeopleToTeamItem.tap(); + + if (isIos()) { + // * Verify share dialog is open + await expect(systemDialog(`Join the ${testTeam.display_name} team`)).toExist(); + } + }); +}); diff --git a/types/database/models/servers/team.ts b/types/database/models/servers/team.ts index 4e8cce42a..7599f0eb4 100644 --- a/types/database/models/servers/team.ts +++ b/types/database/models/servers/team.ts @@ -47,6 +47,9 @@ declare class TeamModel extends Model { /** allowed_domains : List of domains that can join this team */ allowedDomains: string; + /** invite_id : The token id to use in invites to the team */ + inviteId: string; + /** categories : All the categories associated with this team */ categories: Query; From c9a133b39ba40b2065d1ea8cd66190428c3f4398 Mon Sep 17 00:00:00 2001 From: Julian Mondragon Date: Tue, 1 Nov 2022 09:43:03 -0500 Subject: [PATCH 2/6] MM-39720_Invite People - phase 1 Set zephyr test case --- detox/e2e/test/teams/invite_people.e2e.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detox/e2e/test/teams/invite_people.e2e.ts b/detox/e2e/test/teams/invite_people.e2e.ts index 6975899cc..3423006fd 100644 --- a/detox/e2e/test/teams/invite_people.e2e.ts +++ b/detox/e2e/test/teams/invite_people.e2e.ts @@ -58,7 +58,7 @@ describe('Teams - Invite people', () => { await HomeScreen.logout(); }); - it('MM-T4729_2 - should be able to share a URL invite to the team', async () => { + it('MM-T5221 - should be able to share a URL invite to the team', async () => { // # Open plus menu await ChannelListScreen.headerPlusButton.tap(); From eef43619fc4b9470e5c6fc59e7aa8fdf28721945 Mon Sep 17 00:00:00 2001 From: Julian Mondragon Date: Wed, 2 Nov 2022 13:51:17 -0500 Subject: [PATCH 3/6] MM-39720_Invite People - phase 1 Plus menu separator --- app/components/slide_up_panel_item/index.tsx | 12 ++----- .../categories_list/header/header.tsx | 6 +++- .../header/plus_menu/index.tsx | 12 ++++--- .../categories_list/header/plus_menu/item.tsx | 1 - .../header/plus_menu/separator.tsx | 33 +++++++++++++++++++ app/utils/helpers.ts | 4 +-- 6 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 app/screens/home/channel_list/categories_list/header/plus_menu/separator.tsx diff --git a/app/components/slide_up_panel_item/index.tsx b/app/components/slide_up_panel_item/index.tsx index 143483c32..840d8ec5e 100644 --- a/app/components/slide_up_panel_item/index.tsx +++ b/app/components/slide_up_panel_item/index.tsx @@ -21,7 +21,6 @@ type SlideUpPanelProps = { textStyles?: TextStyle; testID?: string; text: string; - topDivider?: boolean; } export const ITEM_HEIGHT = 48; @@ -62,15 +61,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { color: theme.centerChannelColor, ...typography('Body', 200, 'Regular'), }, - divider: { - borderTopWidth: 1, - borderStyle: 'solid', - borderColor: changeOpacity(theme.centerChannelColor, 0.08), - }, }; }); -const SlideUpPanelItem = ({destructive, icon, imageStyles, onPress, testID, text, textStyles, rightIcon = false, topDivider}: SlideUpPanelProps) => { +const SlideUpPanelItem = ({destructive, icon, imageStyles, onPress, testID, text, textStyles, rightIcon = false}: SlideUpPanelProps) => { const theme = useTheme(); const handleOnPress = useCallback(preventDoubleTap(onPress, 500), []); const style = getStyleSheet(theme); @@ -105,12 +99,10 @@ const SlideUpPanelItem = ({destructive, icon, imageStyles, onPress, testID, text } } - const divider = topDivider ? style.divider : {}; - return ( diff --git a/app/screens/home/channel_list/categories_list/header/header.tsx b/app/screens/home/channel_list/categories_list/header/header.tsx index c391deedc..f21d5f81b 100644 --- a/app/screens/home/channel_list/categories_list/header/header.tsx +++ b/app/screens/home/channel_list/categories_list/header/header.tsx @@ -24,6 +24,7 @@ import {typography} from '@utils/typography'; import LoadingUnreads from './loading_unreads'; import PlusMenu from './plus_menu'; +import {SEPARATOR_HEIGHT} from './plus_menu/separator'; type Props = { canCreateChannels: boolean; @@ -131,6 +132,8 @@ const ChannelListHeader = ({ const closeButtonId = 'close-plus-menu'; let items = 1; + let separators = 0; + if (canCreateChannels) { items += 1; } @@ -141,12 +144,13 @@ const ChannelListHeader = ({ if (canInvitePeople) { items += 1; + separators += 1; } bottomSheet({ closeButtonId, renderContent, - snapPoints: [bottomSheetSnapPoint(items, ITEM_HEIGHT, insets.bottom), 10], + snapPoints: [bottomSheetSnapPoint(items, ITEM_HEIGHT, insets.bottom, separators, SEPARATOR_HEIGHT), 10], theme, title: intl.formatMessage({id: 'home.header.plus_menu', defaultMessage: 'Options'}), }); diff --git a/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx b/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx index 35bc9b806..4bf2481bb 100644 --- a/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx +++ b/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx @@ -14,6 +14,7 @@ import {useTheme} from '@context/theme'; import {dismissBottomSheet, showModal} from '@screens/navigation'; import PlusMenuItem from './item'; +import PlusMenuSeparator from './separator'; type Props = { canCreateChannels: boolean; @@ -128,10 +129,13 @@ const PlusMenuList = ({canCreateChannels, canJoinChannels, canInvitePeople, disp onPress={openDirectMessage} /> {canInvitePeople && - + <> + + + } ); diff --git a/app/screens/home/channel_list/categories_list/header/plus_menu/item.tsx b/app/screens/home/channel_list/categories_list/header/plus_menu/item.tsx index 590fa77ae..34c61a2a3 100644 --- a/app/screens/home/channel_list/categories_list/header/plus_menu/item.tsx +++ b/app/screens/home/channel_list/categories_list/header/plus_menu/item.tsx @@ -36,7 +36,6 @@ const PlusMenuItem = ({pickerAction, onPress}: PlusMenuItemProps) => { icon: 'account-plus-outline', text: intl.formatMessage({id: 'plus_menu.invite_people_to_team.title', defaultMessage: 'Invite people to the team'}), testID: 'plus_menu_item.invite_people_to_team', - topDivider: true, }, }; diff --git a/app/screens/home/channel_list/categories_list/header/plus_menu/separator.tsx b/app/screens/home/channel_list/categories_list/header/plus_menu/separator.tsx new file mode 100644 index 000000000..2bed13b13 --- /dev/null +++ b/app/screens/home/channel_list/categories_list/header/plus_menu/separator.tsx @@ -0,0 +1,33 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {View} from 'react-native'; + +import {useTheme} from '@context/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; + +const MARGIN_VERTICAL = 8; +const BORDER = 1; +export const SEPARATOR_HEIGHT = (MARGIN_VERTICAL * 2) + BORDER; + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + separator: { + marginTop: MARGIN_VERTICAL, + marginBottom: MARGIN_VERTICAL, + borderTopWidth: BORDER, + borderStyle: 'solid', + borderColor: changeOpacity(theme.centerChannelColor, 0.08), + }, +})); + +const PlusMenuSeparator = () => { + const theme = useTheme(); + const style = getStyleSheet(theme); + + return ( + + ); +}; + +export default PlusMenuSeparator; diff --git a/app/utils/helpers.ts b/app/utils/helpers.ts index 7f55e26ec..45d937023 100644 --- a/app/utils/helpers.ts +++ b/app/utils/helpers.ts @@ -144,12 +144,12 @@ export async function isTablet() { export const pluckUnique = (key: string) => (array: Array<{[key: string]: unknown}>) => Array.from(new Set(array.map((obj) => obj[key]))); -export function bottomSheetSnapPoint(itemsCount: number, itemHeight: number, bottomInset = 0) { +export function bottomSheetSnapPoint(itemsCount: number, itemHeight: number, bottomInset = 0, separatorCount = 0, separatorHeight = 0) { let bottom = bottomInset; if (!bottom && Platform.OS === 'ios') { bottom = IOS_STATUS_BAR_HEIGHT; } - return ((itemsCount + Platform.select({android: 1, default: 0})) * itemHeight) + (bottom * 2.5); + return ((itemsCount + Platform.select({android: 1, default: 0})) * itemHeight) + (separatorCount * separatorHeight) + (bottom * 2.5); } export function hasTrailingSpaces(term: string) { From 51329fb36c8544144998bd8f235cba2e99b1ffe9 Mon Sep 17 00:00:00 2001 From: Julian Mondragon Date: Thu, 3 Nov 2022 10:15:28 -0500 Subject: [PATCH 4/6] MM-39720_Invite People - phase 1 address feedback --- .../home/channel_list/categories_list/header/header.tsx | 2 +- .../home/channel_list/categories_list/header/index.ts | 8 ++------ app/utils/helpers.ts | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/app/screens/home/channel_list/categories_list/header/header.tsx b/app/screens/home/channel_list/categories_list/header/header.tsx index f21d5f81b..1da5f3626 100644 --- a/app/screens/home/channel_list/categories_list/header/header.tsx +++ b/app/screens/home/channel_list/categories_list/header/header.tsx @@ -150,7 +150,7 @@ const ChannelListHeader = ({ bottomSheet({ closeButtonId, renderContent, - snapPoints: [bottomSheetSnapPoint(items, ITEM_HEIGHT, insets.bottom, separators, SEPARATOR_HEIGHT), 10], + snapPoints: [bottomSheetSnapPoint(items, ITEM_HEIGHT, insets.bottom) + (separators * SEPARATOR_HEIGHT), 10], theme, title: intl.formatMessage({id: 'home.header.plus_menu', defaultMessage: 'Options'}), }); diff --git a/app/screens/home/channel_list/categories_list/header/index.ts b/app/screens/home/channel_list/categories_list/header/index.ts index ef99d7958..4d638cf3d 100644 --- a/app/screens/home/channel_list/categories_list/header/index.ts +++ b/app/screens/home/channel_list/categories_list/header/index.ts @@ -8,7 +8,7 @@ import {switchMap} from 'rxjs/operators'; import {Permissions} from '@constants'; import {observePermissionForTeam} from '@queries/servers/role'; -import {observeConfig, observePushVerificationStatus} from '@queries/servers/system'; +import {observeConfigBooleanValue, observePushVerificationStatus} from '@queries/servers/system'; import {observeCurrentTeam} from '@queries/servers/team'; import {observeCurrentUser} from '@queries/servers/user'; @@ -21,11 +21,7 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { const currentUser = observeCurrentUser(database); - const config = observeConfig(database); - - const enableOpenServer = config.pipe( - switchMap((t) => of$(t?.EnableOpenServer === 'true')), - ); + const enableOpenServer = observeConfigBooleanValue(database, 'EnableOpenServer'); const canJoinChannels = combineLatest([currentUser, team]).pipe( switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true)), diff --git a/app/utils/helpers.ts b/app/utils/helpers.ts index 45d937023..7f55e26ec 100644 --- a/app/utils/helpers.ts +++ b/app/utils/helpers.ts @@ -144,12 +144,12 @@ export async function isTablet() { export const pluckUnique = (key: string) => (array: Array<{[key: string]: unknown}>) => Array.from(new Set(array.map((obj) => obj[key]))); -export function bottomSheetSnapPoint(itemsCount: number, itemHeight: number, bottomInset = 0, separatorCount = 0, separatorHeight = 0) { +export function bottomSheetSnapPoint(itemsCount: number, itemHeight: number, bottomInset = 0) { let bottom = bottomInset; if (!bottom && Platform.OS === 'ios') { bottom = IOS_STATUS_BAR_HEIGHT; } - return ((itemsCount + Platform.select({android: 1, default: 0})) * itemHeight) + (separatorCount * separatorHeight) + (bottom * 2.5); + return ((itemsCount + Platform.select({android: 1, default: 0})) * itemHeight) + (bottom * 2.5); } export function hasTrailingSpaces(term: string) { From ce9b448ebca113e51a89ba90f5c2e7b71e0e3f34 Mon Sep 17 00:00:00 2001 From: Julian Mondragon Date: Thu, 3 Nov 2022 12:14:20 -0500 Subject: [PATCH 5/6] MM-39720_Invite People - phase 1 intl character --- assets/base/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index a5b42d806..1fcf99232 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -315,7 +315,7 @@ "intro.welcome": "Welcome to {displayName} channel.", "intro.welcome.private": "Only invited members can see messages posted in this private channel.", "intro.welcome.public": "Add some more team members to the channel or start a conversation below.", - "invite_people_to_team.message": "Here´s a link to collaborate and communicate with us on Mattermost.", + "invite_people_to_team.message": "Here’s a link to collaborate and communicate with us on Mattermost.", "invite_people_to_team.title": "Join the {team} team", "last_users_message.added_to_channel.type": "were **added to the channel** by {actor}.", "last_users_message.added_to_team.type": "were **added to the team** by {actor}.", From 46b4d46e56c7da8928380c805cffc5f6b0626e59 Mon Sep 17 00:00:00 2001 From: Julian Mondragon Date: Thu, 3 Nov 2022 12:16:20 -0500 Subject: [PATCH 6/6] MM-39720_Invite People - phase 1 intl character --- .../channel_list/categories_list/header/plus_menu/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx b/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx index 4bf2481bb..4fabba5a9 100644 --- a/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx +++ b/app/screens/home/channel_list/categories_list/header/plus_menu/index.tsx @@ -62,7 +62,7 @@ const PlusMenuList = ({canCreateChannels, canJoinChannels, canInvitePeople, disp const url = `${serverUrl}/signup_user_complete/?id=${inviteId}`; const title = intl.formatMessage({id: 'invite_people_to_team.title', defaultMessage: 'Join the {team} team'}, {team: displayName}); - const message = intl.formatMessage({id: 'invite_people_to_team.message', defaultMessage: 'Here´s a link to collaborate and communicate with us on Mattermost.'}); + const message = intl.formatMessage({id: 'invite_people_to_team.message', defaultMessage: 'Here’s a link to collaborate and communicate with us on Mattermost.'}); const icon = 'data:/;base64,'; const options: ShareOptions = Platform.select({