Live location sharing: create beacon info event from location picker (#8072)

* create beacon info event with defaulted duration

Signed-off-by: Kerry Archibald <kerrya@element.io>

* add shareLiveLocation fn

Signed-off-by: Kerry Archibald <kerrya@element.io>

* test share live location

Signed-off-by: Kerry Archibald <kerrya@element.io>

* i18n

Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
Kerry 2022-03-18 10:52:24 +01:00 committed by GitHub
parent 4e4ce65f58
commit cdcf6d0fd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 152 additions and 36 deletions

View file

@ -29,7 +29,7 @@ import Modal from '../../../Modal';
import ErrorDialog from '../dialogs/ErrorDialog';
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
import { findMapStyleUrl } from './findMapStyleUrl';
import { LocationShareType } from './shareLocation';
import { LocationShareType, ShareLocationFn } from './shareLocation';
import { Icon as LocationIcon } from '../../../../res/img/element-icons/location.svg';
import { LocationShareError } from './LocationShareErrors';
import AccessibleButton from '../elements/AccessibleButton';
@ -38,7 +38,7 @@ import { getUserNameColorClass } from '../../../utils/FormattingUtils';
export interface ILocationPickerProps {
sender: RoomMember;
shareType: LocationShareType;
onChoose(uri: string, ts: number): unknown;
onChoose: ShareLocationFn;
onFinished(ev?: SyntheticEvent): void;
}
@ -209,7 +209,7 @@ class LocationPicker extends React.Component<ILocationPickerProps, IState> {
private onOk = () => {
const position = this.state.position;
this.props.onChoose(position ? getGeoUri(position) : undefined, position?.timestamp);
this.props.onChoose(position ? { uri: getGeoUri(position), timestamp: position.timestamp } : {});
this.props.onFinished();
};

View file

@ -21,11 +21,12 @@ import { IEventRelation } from 'matrix-js-sdk/src/models/event';
import MatrixClientContext from '../../../contexts/MatrixClientContext';
import ContextMenu, { AboveLeftOf } from '../../structures/ContextMenu';
import LocationPicker, { ILocationPickerProps } from "./LocationPicker";
import { shareLocation } from './shareLocation';
import { shareLiveLocation, shareLocation } from './shareLocation';
import SettingsStore from '../../../settings/SettingsStore';
import ShareDialogButtons from './ShareDialogButtons';
import ShareType from './ShareType';
import { LocationShareType } from './shareLocation';
import { OwnProfileStore } from '../../../stores/OwnProfileStore';
type Props = Omit<ILocationPickerProps, 'onChoose' | 'shareType'> & {
onFinished: (ev?: SyntheticEvent) => void;
@ -66,20 +67,27 @@ const LocationShareMenu: React.FC<Props> = ({
multipleShareTypesEnabled ? undefined : LocationShareType.Own,
);
const displayName = OwnProfileStore.instance.displayName;
const onLocationSubmit = shareType === LocationShareType.Live ?
shareLiveLocation(matrixClient, roomId, displayName, openMenu) :
shareLocation(matrixClient, roomId, shareType, relation, openMenu);
return <ContextMenu
{...menuPosition}
onFinished={onFinished}
managed={false}
>
<div className="mx_LocationShareMenu">
{ shareType ? <LocationPicker
sender={sender}
shareType={shareType}
onChoose={shareLocation(matrixClient, roomId, shareType, relation, openMenu)}
onFinished={onFinished}
/>
:
<ShareType setShareType={setShareType} enabledShareTypes={enabledShareTypes} /> }
{ shareType ?
<LocationPicker
sender={sender}
shareType={shareType}
onChoose={onLocationSubmit}
onFinished={onFinished}
/> :
<ShareType setShareType={setShareType} enabledShareTypes={enabledShareTypes} />
}
<ShareDialogButtons displayBack={!!shareType && multipleShareTypesEnabled} onBack={() => setShareType(undefined)} onCancel={onFinished} />
</div>
</ContextMenu>;

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import { MatrixClient } from "matrix-js-sdk/src/client";
import { makeLocationContent } from "matrix-js-sdk/src/content-helpers";
import { makeLocationContent, makeBeaconInfoContent } from "matrix-js-sdk/src/content-helpers";
import { logger } from "matrix-js-sdk/src/logger";
import { IEventRelation } from "matrix-js-sdk/src/models/event";
import { LocationAssetType } from "matrix-js-sdk/src/@types/location";
@ -32,38 +32,78 @@ export enum LocationShareType {
Live = 'Live'
}
export type LocationShareProps = {
timeout?: number;
uri?: string;
timestamp?: number;
};
// default duration to 5min for now
const DEFAULT_LIVE_DURATION = 300000;
export type ShareLocationFn = (props: LocationShareProps) => Promise<void>;
const handleShareError = (error: Error, openMenu: () => void, shareType: LocationShareType) => {
const errorMessage = shareType === LocationShareType.Live ?
"We couldn't start sharing your live location" :
"We couldn't send your location";
logger.error(errorMessage, error);
const analyticsAction = errorMessage;
const params = {
title: _t("We couldn't send your location"),
description: _t("%(brand)s could not send your location. Please try again later.", {
brand: SdkConfig.get().brand,
}),
button: _t('Try again'),
cancelButton: _t('Cancel'),
onFinished: (tryAgain: boolean) => {
if (tryAgain) {
openMenu();
}
},
};
Modal.createTrackedDialog(analyticsAction, '', QuestionDialog, params);
};
export const shareLiveLocation = (
client: MatrixClient, roomId: string, displayName: string, openMenu: () => void,
): ShareLocationFn => async ({ timeout }) => {
const description = _t(`%(displayName)s's live location`, { displayName });
try {
await client.unstable_createLiveBeacon(
roomId,
makeBeaconInfoContent(
timeout ?? DEFAULT_LIVE_DURATION,
true, /* isLive */
description,
LocationAssetType.Self,
),
// use timestamp as unique suffix in interim
`${Date.now()}`);
} catch (error) {
handleShareError(error, openMenu, LocationShareType.Live);
}
};
export const shareLocation = (
client: MatrixClient,
roomId: string,
shareType: LocationShareType,
relation: IEventRelation | undefined,
openMenu: () => void,
) => async (uri: string, ts: number) => {
if (!uri) return false;
): ShareLocationFn => async ({ uri, timestamp }) => {
if (!uri) return;
try {
const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation.event_id : null;
const assetType = shareType === LocationShareType.Pin ? LocationAssetType.Pin : LocationAssetType.Self;
await client.sendMessage(roomId, threadId, makeLocationContent(undefined, uri, ts, undefined, assetType));
} catch (e) {
logger.error("We couldn't send your location", e);
const analyticsAction = "We couldn't send your location";
const params = {
title: _t("We couldn't send your location"),
description: _t("%(brand)s could not send your location. Please try again later.", {
brand: SdkConfig.get().brand,
}),
button: _t('Try again'),
cancelButton: _t('Cancel'),
onFinished: (tryAgain: boolean) => {
if (tryAgain) {
openMenu();
}
},
};
Modal.createTrackedDialog(analyticsAction, '', QuestionDialog, params);
await client.sendMessage(
roomId,
threadId,
makeLocationContent(undefined, uri, timestamp, undefined, assetType),
);
} catch (error) {
handleShareError(error, openMenu, shareType);
}
return true;
};
export function textForLocation(