forked from Ivasoft/mattermost-mobile
Compare commits
4 Commits
test1.0.4
...
MM-47655-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b261d0dc16 | ||
|
|
f54bb59aef | ||
|
|
2d10f99a94 | ||
|
|
a6b51436ac |
@@ -530,6 +530,46 @@ export const fetchProfilesInTeam = async (serverUrl: string, teamId: string, pag
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchProfilesNotInChannel = async (
|
||||
serverUrl: string,
|
||||
teamId: string,
|
||||
channelId: string,
|
||||
groupConstrained: boolean,
|
||||
page = 0,
|
||||
perPage: number = General.PROFILE_CHUNK_SIZE,
|
||||
fetchOnly = false,
|
||||
) => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
|
||||
try {
|
||||
const users = await client.getProfilesNotInChannel(teamId, channelId, groupConstrained, page, perPage);
|
||||
|
||||
if (!fetchOnly) {
|
||||
const currentUserId = await getCurrentUserId(operator.database);
|
||||
const toStore = removeUserFromList(currentUserId, users);
|
||||
await operator.handleUsers({
|
||||
users: toStore,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
|
||||
return {users};
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const searchProfiles = async (serverUrl: string, term: string, options: any = {}, fetchOnly = false) => {
|
||||
let client: Client;
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleProp, ViewStyle} from 'react-native';
|
||||
|
||||
import OptionBox from '@components/option_box';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {ChannelModel} from '@database/models/server';
|
||||
import {dismissBottomSheet, goToScreen, showModal} from '@screens/navigation';
|
||||
import {changeOpacity} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
channel: ChannelModel;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
inModal?: boolean;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
const AddPeopleBox = ({channel, containerStyle, inModal, testID}: Props) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const channelId = channel.id;
|
||||
const displayName = channel.displayName;
|
||||
|
||||
const onAddPeople = useCallback(async () => {
|
||||
const title = intl.formatMessage({id: 'mobile.channel_add_people.title', defaultMessage: 'Add Members'});
|
||||
const options = {
|
||||
topBar: {
|
||||
subtitle: {
|
||||
color: changeOpacity(theme.sidebarHeaderTextColor, 0.72),
|
||||
text: displayName,
|
||||
},
|
||||
},
|
||||
};
|
||||
if (inModal) {
|
||||
goToScreen(Screens.CHANNEL_ADD_PEOPLE, title, {channelId}, options);
|
||||
return;
|
||||
}
|
||||
await dismissBottomSheet();
|
||||
showModal(Screens.CHANNEL_ADD_PEOPLE, title, {channelId});
|
||||
}, [intl, channelId, inModal]);
|
||||
|
||||
return (
|
||||
<OptionBox
|
||||
containerStyle={containerStyle}
|
||||
iconName='account-plus-outline'
|
||||
onPress={onAddPeople}
|
||||
testID={testID}
|
||||
text={intl.formatMessage({id: 'intro.add_people', defaultMessage: 'Add People'})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddPeopleBox;
|
||||
@@ -1,43 +1,23 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleProp, ViewStyle} from 'react-native';
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
|
||||
import OptionBox from '@components/option_box';
|
||||
import {Screens} from '@constants';
|
||||
import {dismissBottomSheet, goToScreen, showModal} from '@screens/navigation';
|
||||
import {observeChannel} from '@queries/servers/channel';
|
||||
|
||||
type Props = {
|
||||
import AddPeopleBox from './add_people_box';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
type Props = WithDatabaseArgs & {
|
||||
channelId: string;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
inModal?: boolean;
|
||||
testID?: string;
|
||||
}
|
||||
|
||||
const AddPeopleBox = ({channelId, containerStyle, inModal, testID}: Props) => {
|
||||
const intl = useIntl();
|
||||
const enhanced = withObservables(['channelId'], ({channelId, database}: Props) => {
|
||||
return {
|
||||
channel: observeChannel(database, channelId),
|
||||
};
|
||||
});
|
||||
|
||||
const onAddPeople = useCallback(async () => {
|
||||
const title = intl.formatMessage({id: 'intro.add_people', defaultMessage: 'Add People'});
|
||||
if (inModal) {
|
||||
goToScreen(Screens.CHANNEL_ADD_PEOPLE, title, {channelId});
|
||||
return;
|
||||
}
|
||||
await dismissBottomSheet();
|
||||
showModal(Screens.CHANNEL_ADD_PEOPLE, title, {channelId});
|
||||
}, [intl, channelId, inModal]);
|
||||
|
||||
return (
|
||||
<OptionBox
|
||||
containerStyle={containerStyle}
|
||||
iconName='account-plus-outline'
|
||||
onPress={onAddPeople}
|
||||
testID={testID}
|
||||
text={intl.formatMessage({id: 'intro.add_people', defaultMessage: 'Add People'})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddPeopleBox;
|
||||
export default withDatabase(enhanced(AddPeopleBox));
|
||||
|
||||
@@ -126,6 +126,7 @@ export default {
|
||||
export const MODAL_SCREENS_WITHOUT_BACK = new Set<string>([
|
||||
BROWSE_CHANNELS,
|
||||
CHANNEL_INFO,
|
||||
CHANNEL_ADD_PEOPLE,
|
||||
CREATE_DIRECT_MESSAGE,
|
||||
CREATE_TEAM,
|
||||
CUSTOM_STATUS,
|
||||
@@ -156,7 +157,6 @@ export const OVERLAY_SCREENS = new Set<string>([
|
||||
]);
|
||||
|
||||
export const NOT_READY = [
|
||||
CHANNEL_ADD_PEOPLE,
|
||||
CHANNEL_MENTION,
|
||||
CREATE_TEAM,
|
||||
INTEGRATION_SELECTOR,
|
||||
|
||||
340
app/screens/channel_add_people/channel_add_people.tsx
Normal file
340
app/screens/channel_add_people/channel_add_people.tsx
Normal file
@@ -0,0 +1,340 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react';
|
||||
import {defineMessages, useIntl} from 'react-intl';
|
||||
import {Keyboard, Platform, StyleSheet, View} from 'react-native';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {addMembersToChannel} from '@actions/remote/channel';
|
||||
import {fetchProfilesNotInChannel, searchProfiles} from '@actions/remote/user';
|
||||
import Loading from '@components/loading';
|
||||
import Search from '@components/search';
|
||||
import SelectedUsers from '@components/selected_users_panel';
|
||||
import UserList from '@components/user_list';
|
||||
import {General} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {ChannelModel} from '@database/models/server';
|
||||
import {debounce} from '@helpers/api/general';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {t} from '@i18n';
|
||||
import {popTopScreen, setButtons} from '@screens/navigation';
|
||||
import {alertErrorWithFallback} from '@utils/draft';
|
||||
import {changeOpacity, getKeyboardAppearanceFromTheme} from '@utils/theme';
|
||||
import {filterProfilesMatchingTerm} from '@utils/user';
|
||||
|
||||
const ADD_BUTTON = 'add-button';
|
||||
|
||||
const close = () => {
|
||||
Keyboard.dismiss();
|
||||
popTopScreen();
|
||||
};
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
searchBar: {
|
||||
marginLeft: 12,
|
||||
marginRight: Platform.select({ios: 4, default: 12}),
|
||||
marginVertical: 12,
|
||||
},
|
||||
});
|
||||
|
||||
const messages = defineMessages({
|
||||
error: {
|
||||
id: t('mobile.channel_add_people.error'),
|
||||
defaultMessage: 'We could not add those users to the channel. Please check your connection and try again.',
|
||||
},
|
||||
button: {
|
||||
id: t('mobile.channel_add_people.title'),
|
||||
defaultMessage: 'Add Members',
|
||||
},
|
||||
});
|
||||
|
||||
function reduceProfiles(state: UserProfile[], action: {type: 'add'; values?: UserProfile[]}) {
|
||||
if (action.type === 'add' && action.values?.length) {
|
||||
return [...state, ...action.values];
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
type Props = {
|
||||
componentId: string;
|
||||
currentChannel: ChannelModel;
|
||||
currentTeamId: string;
|
||||
currentUserId: string;
|
||||
restrictDirectMessage: boolean;
|
||||
teammateNameDisplay: string;
|
||||
tutorialWatched: boolean;
|
||||
}
|
||||
|
||||
export default function ChannelAddPeople({
|
||||
componentId,
|
||||
currentChannel,
|
||||
currentTeamId,
|
||||
currentUserId,
|
||||
restrictDirectMessage,
|
||||
teammateNameDisplay,
|
||||
tutorialWatched,
|
||||
}: Props) {
|
||||
const serverUrl = useServerUrl();
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const {formatMessage} = intl;
|
||||
|
||||
const searchTimeoutId = useRef<NodeJS.Timeout | null>(null);
|
||||
const next = useRef(true);
|
||||
const page = useRef(-1);
|
||||
const mounted = useRef(false);
|
||||
|
||||
const [profiles, dispatchProfiles] = useReducer(reduceProfiles, []);
|
||||
const [searchResults, setSearchResults] = useState<UserProfile[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [term, setTerm] = useState('');
|
||||
const [startingConversation, setStartingConversation] = useState(false);
|
||||
const [selectedIds, setSelectedIds] = useState<{[id: string]: UserProfile}>({});
|
||||
const selectedCount = Object.keys(selectedIds).length;
|
||||
const groupConstrained = currentChannel.isGroupConstrained;
|
||||
const currentChannelId = currentChannel.id;
|
||||
|
||||
const isSearch = Boolean(term);
|
||||
|
||||
const loadedProfiles = ({users}: {users?: UserProfile[]}) => {
|
||||
if (mounted.current) {
|
||||
if (users && !users.length) {
|
||||
next.current = false;
|
||||
}
|
||||
|
||||
page.current += 1;
|
||||
setLoading(false);
|
||||
dispatchProfiles({type: 'add', values: users});
|
||||
}
|
||||
};
|
||||
|
||||
const getProfiles = useCallback(debounce(() => {
|
||||
if (next.current && !loading && !term && mounted.current) {
|
||||
setLoading(true);
|
||||
fetchProfilesNotInChannel(serverUrl,
|
||||
currentTeamId,
|
||||
currentChannelId,
|
||||
groupConstrained,
|
||||
page.current + 1,
|
||||
General.PROFILE_CHUNK_SIZE).then(loadedProfiles);
|
||||
}
|
||||
}, 100), [loading, isSearch, serverUrl, currentTeamId]);
|
||||
|
||||
const handleRemoveProfile = useCallback((id: string) => {
|
||||
const newSelectedIds = Object.assign({}, selectedIds);
|
||||
|
||||
Reflect.deleteProperty(newSelectedIds, id);
|
||||
|
||||
setSelectedIds(newSelectedIds);
|
||||
}, [selectedIds]);
|
||||
|
||||
const addPeopleToChannel = useCallback(async (ids: string[]): Promise<boolean> => {
|
||||
const result = await addMembersToChannel(serverUrl, currentChannelId, ids, '', false);
|
||||
|
||||
if (result.error) {
|
||||
alertErrorWithFallback(intl, result.error, messages.error);
|
||||
}
|
||||
|
||||
return !result.error;
|
||||
}, [serverUrl]);
|
||||
|
||||
const clearSearch = useCallback(() => {
|
||||
setTerm('');
|
||||
setSearchResults([]);
|
||||
}, []);
|
||||
|
||||
const startConversation = useCallback(async (selectedId?: {[id: string]: boolean}) => {
|
||||
if (startingConversation) {
|
||||
return;
|
||||
}
|
||||
|
||||
setStartingConversation(true);
|
||||
|
||||
const idsToUse = selectedId ? Object.keys(selectedId) : Object.keys(selectedIds);
|
||||
let success;
|
||||
if (idsToUse.length === 0) {
|
||||
success = false;
|
||||
} else {
|
||||
success = await addPeopleToChannel(idsToUse);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
close();
|
||||
} else {
|
||||
setStartingConversation(false);
|
||||
}
|
||||
}, [startingConversation, selectedIds, addPeopleToChannel]);
|
||||
|
||||
const handleSelectProfile = useCallback((user: UserProfile) => {
|
||||
if (selectedIds[user.id]) {
|
||||
handleRemoveProfile(user.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.id === currentUserId) {
|
||||
const selectedId = {
|
||||
[currentUserId]: true,
|
||||
};
|
||||
|
||||
startConversation(selectedId);
|
||||
} else {
|
||||
const wasSelected = selectedIds[user.id];
|
||||
if (!wasSelected && selectedCount >= General.MAX_USERS_IN_GM) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newSelectedIds = Object.assign({}, selectedIds);
|
||||
if (!wasSelected) {
|
||||
newSelectedIds[user.id] = user;
|
||||
}
|
||||
|
||||
setSelectedIds(newSelectedIds);
|
||||
clearSearch();
|
||||
}
|
||||
}, [selectedIds, currentUserId, handleRemoveProfile, startConversation, clearSearch]);
|
||||
|
||||
const searchUsers = useCallback(async (searchTerm: string) => {
|
||||
const lowerCasedTerm = searchTerm.toLowerCase();
|
||||
setLoading(true);
|
||||
|
||||
const results = await searchProfiles(serverUrl, lowerCasedTerm, {
|
||||
team_id: currentTeamId,
|
||||
allow_inactive: true,
|
||||
});
|
||||
|
||||
let data: UserProfile[] = [];
|
||||
if (results.data) {
|
||||
data = results.data;
|
||||
}
|
||||
|
||||
setSearchResults(data);
|
||||
setLoading(false);
|
||||
}, [restrictDirectMessage, serverUrl, currentTeamId]);
|
||||
|
||||
const search = useCallback(() => {
|
||||
searchUsers(term);
|
||||
}, [searchUsers, term]);
|
||||
|
||||
const onSearch = useCallback((text: string) => {
|
||||
if (text) {
|
||||
setTerm(text);
|
||||
if (searchTimeoutId.current) {
|
||||
clearTimeout(searchTimeoutId.current);
|
||||
}
|
||||
|
||||
searchTimeoutId.current = setTimeout(() => {
|
||||
searchUsers(text);
|
||||
}, General.SEARCH_TIMEOUT_MILLISECONDS);
|
||||
} else {
|
||||
clearSearch();
|
||||
}
|
||||
}, [searchUsers, clearSearch]);
|
||||
|
||||
const updateNavigationButtons = useCallback(async (startEnabled: boolean) => {
|
||||
setButtons(componentId, {
|
||||
rightButtons: [{
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
id: ADD_BUTTON,
|
||||
text: formatMessage({id: 'mobile.channel_add_people.button', defaultMessage: 'Add'}),
|
||||
showAsAction: 'always',
|
||||
enabled: startEnabled,
|
||||
testID: 'add_members.start.button',
|
||||
}],
|
||||
});
|
||||
}, [intl.locale, theme]);
|
||||
|
||||
useNavButtonPressed(ADD_BUTTON, componentId, startConversation, [startConversation]);
|
||||
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
updateNavigationButtons(false);
|
||||
getProfiles();
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const canStart = selectedCount > 0 && !startingConversation;
|
||||
updateNavigationButtons(canStart);
|
||||
}, [selectedCount > 0, startingConversation, updateNavigationButtons]);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (term) {
|
||||
const exactMatches: UserProfile[] = [];
|
||||
const filterByTerm = (p: UserProfile) => {
|
||||
if (selectedCount > 0 && p.id === currentUserId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p.username === term || p.username.startsWith(term)) {
|
||||
exactMatches.push(p);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const results = filterProfilesMatchingTerm(searchResults, term).filter(filterByTerm);
|
||||
return [...exactMatches, ...results];
|
||||
}
|
||||
return profiles;
|
||||
}, [term, isSearch && selectedCount, isSearch && searchResults, profiles]);
|
||||
|
||||
if (startingConversation) {
|
||||
return (
|
||||
<View style={style.container}>
|
||||
<Loading color={theme.centerChannelColor}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={style.container}
|
||||
testID='add_members.screen'
|
||||
>
|
||||
<View style={style.searchBar}>
|
||||
<Search
|
||||
testID='add_members.search_bar'
|
||||
placeholder={intl.formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
cancelButtonTitle={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
onChangeText={onSearch}
|
||||
onSubmitEditing={search}
|
||||
onCancel={clearSearch}
|
||||
autoCapitalize='none'
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
value={term}
|
||||
/>
|
||||
</View>
|
||||
{selectedCount > 0 &&
|
||||
<SelectedUsers
|
||||
selectedIds={selectedIds}
|
||||
warnCount={General.MAX_USERS_IN_GM - 2}
|
||||
maxCount={General.MAX_USERS_IN_GM}
|
||||
onRemove={handleRemoveProfile}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
/>
|
||||
}
|
||||
<UserList
|
||||
currentUserId={currentUserId}
|
||||
handleSelectProfile={handleSelectProfile}
|
||||
loading={loading}
|
||||
profiles={data}
|
||||
selectedIds={selectedIds}
|
||||
showNoResults={!loading && page.current !== -1}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
fetchMore={getProfiles}
|
||||
term={term}
|
||||
testID='add_members.user_list'
|
||||
tutorialWatched={tutorialWatched}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
25
app/screens/channel_add_people/index.tsx
Normal file
25
app/screens/channel_add_people/index.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
|
||||
import {observeCurrentTeamId} from '@app/queries/servers/system';
|
||||
import {observeProfileLongPresTutorial} from '@queries/app/global';
|
||||
import {observeCurrentChannel} from '@queries/servers/channel';
|
||||
import {observeTeammateNameDisplay} from '@queries/servers/user';
|
||||
|
||||
import ChannelAddPeople from './channel_add_people';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
return {
|
||||
currentChannel: observeCurrentChannel(database),
|
||||
currentTeamId: observeCurrentTeamId(database),
|
||||
teammateNameDisplay: observeTeammateNameDisplay(database),
|
||||
tutorialWatched: observeProfileLongPresTutorial(),
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(enhanced(ChannelAddPeople));
|
||||
@@ -11,6 +11,8 @@ import {fetchProfiles, fetchProfilesInTeam, searchProfiles} from '@actions/remot
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import Loading from '@components/loading';
|
||||
import Search from '@components/search';
|
||||
import SelectedUsers from '@components/selected_users_panel';
|
||||
import UserList from '@components/user_list';
|
||||
import {General} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
@@ -22,9 +24,6 @@ import {alertErrorWithFallback} from '@utils/draft';
|
||||
import {changeOpacity, getKeyboardAppearanceFromTheme, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {displayUsername, filterProfilesMatchingTerm} from '@utils/user';
|
||||
|
||||
import SelectedUsers from './selected_users';
|
||||
import UserList from './user_list';
|
||||
|
||||
const START_BUTTON = 'start-conversation';
|
||||
const CLOSE_BUTTON = 'close-dms';
|
||||
|
||||
|
||||
@@ -97,6 +97,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
|
||||
case Screens.CREATE_DIRECT_MESSAGE:
|
||||
screen = withServerDatabase(require('@screens/create_direct_message').default);
|
||||
break;
|
||||
case Screens.CHANNEL_ADD_PEOPLE:
|
||||
screen = withServerDatabase(require('@screens/channel_add_people').default);
|
||||
break;
|
||||
case Screens.EDIT_POST:
|
||||
screen = withServerDatabase(require('@screens/edit_post').default);
|
||||
break;
|
||||
|
||||
@@ -398,6 +398,9 @@
|
||||
"mobile.calls_you": "(you)",
|
||||
"mobile.camera_photo_permission_denied_description": "Take photos and upload them to your server or save them to your device. Open Settings to grant {applicationName} read and write access to your camera.",
|
||||
"mobile.camera_photo_permission_denied_title": "{applicationName} would like to access your camera",
|
||||
"mobile.channel_add_people.error": "We could not add those users to the channel. Please check your connection and try again.",
|
||||
"mobile.channel_add_people.title": "Add Members",
|
||||
"mobile.channel_add_people.button": "Add",
|
||||
"mobile.channel_info.alertNo": "No",
|
||||
"mobile.channel_info.alertYes": "Yes",
|
||||
"mobile.channel_list.recent": "Recent",
|
||||
|
||||
Reference in New Issue
Block a user