Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Live location sharing: beacon list view tiles (#8363)
Browse files Browse the repository at this point in the history
* add basic sidebar container

Signed-off-by: Kerry Archibald <[email protected]>

* optionally show icon in beaconstatus

Signed-off-by: Kerry Archibald <[email protected]>

* add avatar and style list item

Signed-off-by: Kerry Archibald <[email protected]>

* formatted last update time

Signed-off-by: Kerry Archibald <[email protected]>

* test beacon list item

Signed-off-by: Kerry Archibald <[email protected]>

* move makeRoomWithState events to test utils

Signed-off-by: Kerry Archibald <[email protected]>

* move beacon test helpers into utils

Signed-off-by: Kerry Archibald <[email protected]>

* newline

Signed-off-by: Kerry Archibald <[email protected]>

* add copyable text to beacon list item

Signed-off-by: Kerry Archibald <[email protected]>

* add copyable geo uri to list item

Signed-off-by: Kerry Archibald <[email protected]>

* improve spacing

Signed-off-by: Kerry Archibald <[email protected]>

* overflow scroll on list

Signed-off-by: Kerry Archibald <[email protected]>
  • Loading branch information
Kerry authored Apr 20, 2022
1 parent 2f6b767 commit 4a38cbd
Show file tree
Hide file tree
Showing 18 changed files with 355 additions and 10 deletions.
1 change: 1 addition & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@import "./_font-sizes.scss";
@import "./_font-weights.scss";
@import "./_spacing.scss";
@import "./components/views/beacon/_BeaconListItem.scss";
@import "./components/views/beacon/_BeaconStatus.scss";
@import "./components/views/beacon/_BeaconViewDialog.scss";
@import "./components/views/beacon/_DialogSidebar.scss";
Expand Down
61 changes: 61 additions & 0 deletions res/css/components/views/beacon/_BeaconListItem.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_BeaconListItem {
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: flex-start;
padding: $spacing-12 0;

border-bottom: 1px solid $system;
}

.mx_BeaconListItem_avatarIcon {
flex: 0 0;
height: 32px;
width: 32px;
}

.mx_BeaconListItem_avatar {
flex: 0 0;
box-sizing: border-box;

margin-right: $spacing-8;
border: 2px solid $location-live-color;
}

.mx_BeaconListItem_info {
flex: 1 1 0;
display: flex;
flex-direction: column;
align-items: stretch;
}

.mx_BeaconListItem_status {
// override beacon status padding
padding: 0 !important;
margin-bottom: $spacing-8;

.mx_BeaconStatus_label {
font-weight: $font-semi-bold;
}
}

.mx_BeaconListItem_lastUpdated {
color: $tertiary-content;
font-size: $font-10px;
}
4 changes: 4 additions & 0 deletions res/css/components/views/beacon/_BeaconStatus.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,7 @@ limitations under the License.
.mx_BeaconStatus_expiryTime {
color: $secondary-content;
}

.mx_BeaconStatus_label {
margin-bottom: 2px;
}
5 changes: 4 additions & 1 deletion res/css/components/views/beacon/_DialogSidebar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ limitations under the License.
height: 100%;
width: 265px;

display: flex;
flex-direction: column;

box-sizing: border-box;
padding: $spacing-16;

Expand All @@ -34,7 +37,7 @@ limitations under the License.
align-items: center;
justify-content: space-between;

flex: 0;
flex: 0 0;
margin-bottom: $spacing-16;

color: $primary-content;
Expand Down
82 changes: 82 additions & 0 deletions src/components/views/beacon/BeaconListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { useContext } from 'react';
import { Beacon, BeaconEvent } from 'matrix-js-sdk/src/matrix';
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';

import MatrixClientContext from '../../../contexts/MatrixClientContext';
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
import { humanizeTime } from '../../../utils/humanize';
import { _t } from '../../../languageHandler';
import MemberAvatar from '../avatars/MemberAvatar';
import CopyableText from '../elements/CopyableText';
import BeaconStatus from './BeaconStatus';
import { BeaconDisplayStatus } from './displayStatus';
import StyledLiveBeaconIcon from './StyledLiveBeaconIcon';

interface Props {
beacon: Beacon;
}

const BeaconListItem: React.FC<Props> = ({ beacon }) => {
const latestLocationState = useEventEmitterState(
beacon,
BeaconEvent.LocationUpdate,
() => beacon.latestLocationState,
);
const matrixClient = useContext(MatrixClientContext);
const room = matrixClient.getRoom(beacon.roomId);

if (!latestLocationState || !beacon.isLive) {
return null;
}

const isSelfLocation = beacon.beaconInfo.assetType === LocationAssetType.Self;
const beaconMember = isSelfLocation ?
room.getMember(beacon.beaconInfoOwner) :
undefined;

const humanizedUpdateTime = humanizeTime(latestLocationState.timestamp);

return <li className='mx_BeaconListItem'>
{ isSelfLocation ?
<MemberAvatar
className='mx_BeaconListItem_avatar'
member={beaconMember}
height={32}
width={32}
/> :
<StyledLiveBeaconIcon className='mx_BeaconListItem_avatarIcon' />
}
<div className='mx_BeaconListItem_info'>
<BeaconStatus
className='mx_BeaconListItem_status'
beacon={beacon}
label={beaconMember?.name || beacon.beaconInfo.description || beacon.beaconInfoOwner}
displayStatus={BeaconDisplayStatus.Active}
>
<CopyableText
border={false}
getTextToCopy={() => latestLocationState?.uri}
/>
</BeaconStatus>
<span className='mx_BeaconListItem_lastUpdated'>{ _t("Updated %(humanizedUpdateTime)s", { humanizedUpdateTime }) }</span>
</div>
</li>;
};

export default BeaconListItem;
8 changes: 5 additions & 3 deletions src/components/views/beacon/BeaconStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { formatTime } from '../../../DateUtils';
interface Props {
displayStatus: BeaconDisplayStatus;
displayLiveTimeRemaining?: boolean;
withIcon?: boolean;
beacon?: Beacon;
label?: string;
}
Expand All @@ -45,6 +46,7 @@ const BeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> =
label,
className,
children,
withIcon,
...rest
}) => {
const isIdle = displayStatus === BeaconDisplayStatus.Loading ||
Expand All @@ -54,11 +56,11 @@ const BeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> =
{...rest}
className={classNames('mx_BeaconStatus', `mx_BeaconStatus_${displayStatus}`, className)}
>
<StyledLiveBeaconIcon
{ withIcon && <StyledLiveBeaconIcon
className='mx_BeaconStatus_icon'
withError={displayStatus === BeaconDisplayStatus.Error}
isIdle={isIdle}
/>
/> }
<div className='mx_BeaconStatus_description'>

{ displayStatus === BeaconDisplayStatus.Loading && <span>{ _t('Loading live location...') }</span> }
Expand All @@ -68,7 +70,7 @@ const BeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> =

{ displayStatus === BeaconDisplayStatus.Active && beacon && <>
<>
{ label }
<span className='mx_BeaconStatus_label'>{ label }</span>
{ displayLiveTimeRemaining ?
<LiveTimeRemaining beacon={beacon} /> :
<BeaconExpiryTime beacon={beacon} />
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/beacon/DialogSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Icon as CloseIcon } from '../../../../res/img/image-view/close.svg';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
import Heading from '../typography/Heading';
import BeaconListItem from './BeaconListItem';

interface Props {
beacons: Beacon[];
Expand All @@ -41,8 +42,7 @@ const DialogSidebar: React.FC<Props> = ({ beacons, requestClose }) => {
</AccessibleButton>
</div>
<ol className='mx_DialogSidebar_list'>
{ /* TODO nice elements */ }
{ beacons.map((beacon, index) => <li key={beacon.identifier}>{ index }</li>) }
{ beacons.map((beacon) => <BeaconListItem key={beacon.identifier} beacon={beacon} />) }
</ol>
</div>;
};
Expand Down
1 change: 1 addition & 0 deletions src/components/views/beacon/OwnBeaconStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
displayStatus={ownDisplayStatus}
label={_t('Live location enabled')}
displayLiveTimeRemaining
withIcon
{...rest}
>
{ ownDisplayStatus === BeaconDisplayStatus.Active && <AccessibleButton
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/elements/CopyableText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { ButtonEvent } from "./AccessibleButton";
import AccessibleTooltipButton from "./AccessibleTooltipButton";

interface IProps {
children: React.ReactNode;
children?: React.ReactNode;
getTextToCopy: () => string;
border?: boolean;
}
Expand Down
1 change: 1 addition & 0 deletions src/components/views/messages/MBeaconBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent }, ref) =>
beacon={beacon}
displayStatus={displayStatus}
label={_t('View live location')}
withIcon
/>
}
</div>
Expand Down
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2913,6 +2913,7 @@
"Click for more info": "Click for more info",
"Beta": "Beta",
"Join the beta": "Join the beta",
"Updated %(humanizedUpdateTime)s": "Updated %(humanizedUpdateTime)s",
"Live until %(expiryTime)s": "Live until %(expiryTime)s",
"Loading live location...": "Loading live location...",
"Live location ended": "Live location ended",
Expand Down
2 changes: 1 addition & 1 deletion src/utils/humanize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const HOURS_1_DAY = 26;
* @returns {string} The humanized time.
*/
export function humanizeTime(timeMillis: number): string {
const now = (new Date()).getTime();
const now = Date.now();
let msAgo = now - timeMillis;
const minutes = Math.abs(Math.ceil(msAgo / 60000));
const hours = Math.ceil(minutes / 60);
Expand Down
Loading

0 comments on commit 4a38cbd

Please sign in to comment.