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

Commit f2ec465

Browse files
author
Kerry
authored
Live location sharing: own live beacon status on maximised view (#8374)
* add floating own live sharing eacon status to maximised view Signed-off-by: Kerry Archibald <[email protected]> * add tests for own beacon status Signed-off-by: Kerry Archibald <[email protected]> * stylelint Signed-off-by: Kerry Archibald <[email protected]> * remove huge snapshot Signed-off-by: Kerry Archibald <[email protected]> * remove unused emits from test Signed-off-by: Kerry Archibald <[email protected]>
1 parent 605fbd3 commit f2ec465

File tree

7 files changed

+175
-16
lines changed

7 files changed

+175
-16
lines changed

res/css/_components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@import "./components/views/beacon/_BeaconListItem.scss";
88
@import "./components/views/beacon/_BeaconStatus.scss";
99
@import "./components/views/beacon/_BeaconViewDialog.scss";
10+
@import "./components/views/beacon/_DialogOwnBeaconStatus.scss";
1011
@import "./components/views/beacon/_DialogSidebar.scss";
1112
@import "./components/views/beacon/_LeftPanelLiveShareWarning.scss";
1213
@import "./components/views/beacon/_LiveTimeRemaining.scss";
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
.mx_DialogOwnBeaconStatus {
18+
position: absolute;
19+
bottom: $spacing-32;
20+
width: 300px;
21+
margin-left: -150px;
22+
left: 50%;
23+
24+
box-sizing: border-box;
25+
display: flex;
26+
flex-direction: row;
27+
align-items: flex-start;
28+
justify-content: stretch;
29+
30+
background: $background;
31+
border-radius: 8px;
32+
box-shadow: 4px 4px 12px 0 $menu-box-shadow-color;
33+
34+
padding: 0 $spacing-12;
35+
}
36+
37+
.mx_DialogOwnBeaconStatus_avatarIcon {
38+
flex: 0 0;
39+
height: 32px;
40+
width: 32px;
41+
margin: $spacing-8 0 $spacing-8 0;
42+
}
43+
44+
.mx_DialogOwnBeaconStatus_avatar {
45+
flex: 0 0;
46+
box-sizing: border-box;
47+
48+
border: 2px solid $location-live-color;
49+
margin: $spacing-8 0 $spacing-8 0;
50+
}
51+
52+
.mx_DialogOwnBeaconStatus_status {
53+
flex: 1 1;
54+
padding-right: 0;
55+
}

src/components/views/beacon/BeaconViewDialog.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { Icon as LocationIcon } from '../../../../res/img/element-icons/location
3636
import { _t } from '../../../languageHandler';
3737
import AccessibleButton from '../elements/AccessibleButton';
3838
import DialogSidebar from './DialogSidebar';
39+
import DialogOwnBeaconStatus from './DialogOwnBeaconStatus';
3940

4041
interface IProps extends IDialogProps {
4142
roomId: Room['roomId'];
@@ -124,6 +125,7 @@ const BeaconViewDialog: React.FC<IProps> = ({
124125
{ _t('View list') }
125126
</AccessibleButton>
126127
}
128+
<DialogOwnBeaconStatus roomId={roomId} />
127129
</MatrixClientContext.Provider>
128130
</BaseDialog>
129131
);
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React, { useContext } from 'react';
18+
import { Room, Beacon } from 'matrix-js-sdk/src/matrix';
19+
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';
20+
21+
import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../stores/OwnBeaconStore';
22+
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
23+
import { OwnProfileStore } from '../../../stores/OwnProfileStore';
24+
import OwnBeaconStatus from './OwnBeaconStatus';
25+
import { BeaconDisplayStatus } from './displayStatus';
26+
import MatrixClientContext from '../../../contexts/MatrixClientContext';
27+
import MemberAvatar from '../avatars/MemberAvatar';
28+
import StyledLiveBeaconIcon from './StyledLiveBeaconIcon';
29+
30+
interface Props {
31+
roomId: Room['roomId'];
32+
}
33+
34+
const useOwnBeacon = (roomId: Room['roomId']): Beacon | undefined => {
35+
const ownBeacon = useEventEmitterState(
36+
OwnProfileStore.instance,
37+
OwnBeaconStoreEvent.LivenessChange,
38+
() => {
39+
const [ownBeaconId] = OwnBeaconStore.instance.getLiveBeaconIds(roomId);
40+
return OwnBeaconStore.instance.getBeaconById(ownBeaconId);
41+
},
42+
);
43+
44+
return ownBeacon;
45+
};
46+
47+
const DialogOwnBeaconStatus: React.FC<Props> = ({ roomId }) => {
48+
const beacon = useOwnBeacon(roomId);
49+
50+
const matrixClient = useContext(MatrixClientContext);
51+
const room = matrixClient.getRoom(roomId);
52+
53+
if (!beacon?.isLive) {
54+
return null;
55+
}
56+
57+
const isSelfLocation = beacon.beaconInfo.assetType === LocationAssetType.Self;
58+
const beaconMember = isSelfLocation ?
59+
room.getMember(beacon.beaconInfoOwner) :
60+
undefined;
61+
62+
return <div className='mx_DialogOwnBeaconStatus'>
63+
{ isSelfLocation ?
64+
<MemberAvatar
65+
className='mx_DialogOwnBeaconStatus_avatar'
66+
member={beaconMember}
67+
height={32}
68+
width={32}
69+
/> :
70+
<StyledLiveBeaconIcon className='mx_DialogOwnBeaconStatus_avatarIcon' />
71+
}
72+
<OwnBeaconStatus
73+
className='mx_DialogOwnBeaconStatus_status'
74+
beacon={beacon}
75+
displayStatus={BeaconDisplayStatus.Active}
76+
/>
77+
</div>;
78+
};
79+
80+
export default DialogOwnBeaconStatus;

src/components/views/beacon/OwnBeaconStatus.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,17 @@ import AccessibleButton from '../elements/AccessibleButton';
2525

2626
interface Props {
2727
displayStatus: BeaconDisplayStatus;
28+
className?: string;
2829
beacon?: Beacon;
30+
withIcon?: boolean;
2931
}
3032

3133
/**
3234
* Wraps BeaconStatus with more capabilities
3335
* for errors and actions available for users own live beacons
3436
*/
3537
const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
36-
beacon, displayStatus, className, ...rest
38+
beacon, displayStatus, ...rest
3739
}) => {
3840
const {
3941
hasWireError,
@@ -49,12 +51,10 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
4951
displayStatus;
5052

5153
return <BeaconStatus
52-
className='mx_MBeaconBody_chin'
5354
beacon={beacon}
5455
displayStatus={ownDisplayStatus}
5556
label={_t('Live location enabled')}
5657
displayLiveTimeRemaining
57-
withIcon
5858
{...rest}
5959
>
6060
{ ownDisplayStatus === BeaconDisplayStatus.Active && <AccessibleButton

test/components/views/beacon/BeaconViewDialog-test.tsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
makeRoomWithStateEvents,
3535
} from '../../../test-utils';
3636
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
37+
import { OwnBeaconStore } from '../../../../src/stores/OwnBeaconStore';
38+
import { BeaconDisplayStatus } from '../../../../src/components/views/beacon/displayStatus';
3739

3840
describe('<BeaconViewDialog />', () => {
3941
// 14.03.2022 16:15
@@ -50,9 +52,10 @@ describe('<BeaconViewDialog />', () => {
5052
getClientWellKnown: jest.fn().mockReturnValue({
5153
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
5254
}),
53-
getUserId: jest.fn().mockReturnValue(aliceId),
55+
getUserId: jest.fn().mockReturnValue(bobId),
5456
getRoom: jest.fn(),
5557
isGuest: jest.fn().mockReturnValue(false),
58+
getVisibleRooms: jest.fn().mockReturnValue([]),
5659
});
5760

5861
// make fresh rooms every time
@@ -83,6 +86,10 @@ describe('<BeaconViewDialog />', () => {
8386
const getComponent = (props = {}) =>
8487
mount(<BeaconViewDialog {...defaultProps} {...props} />);
8588

89+
beforeEach(() => {
90+
jest.spyOn(OwnBeaconStore.instance, 'getLiveBeaconIds').mockRestore();
91+
});
92+
8693
it('renders a map with markers', () => {
8794
const room = setupRoom([defaultEvent]);
8895
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
@@ -95,6 +102,31 @@ describe('<BeaconViewDialog />', () => {
95102
expect(component.find('SmartMarker').length).toEqual(1);
96103
});
97104

105+
it('does not render any own beacon status when user is not live sharing', () => {
106+
// default event belongs to alice, we are bob
107+
const room = setupRoom([defaultEvent]);
108+
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
109+
beacon.addLocations([location1]);
110+
const component = getComponent();
111+
expect(component.find('DialogOwnBeaconStatus').html()).toBeNull();
112+
});
113+
114+
it('renders own beacon status when user is live sharing', () => {
115+
// default event belongs to alice
116+
const room = setupRoom([defaultEvent]);
117+
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
118+
beacon.addLocations([location1]);
119+
// mock own beacon store to show default event as alice's live beacon
120+
jest.spyOn(OwnBeaconStore.instance, 'getLiveBeaconIds').mockReturnValue([beacon.identifier]);
121+
jest.spyOn(OwnBeaconStore.instance, 'getBeaconById').mockReturnValue(beacon);
122+
const component = getComponent();
123+
expect(component.find('MemberAvatar').length).toBeTruthy();
124+
expect(component.find('OwnBeaconStatus').props()).toEqual({
125+
beacon, displayStatus: BeaconDisplayStatus.Active,
126+
className: 'mx_DialogOwnBeaconStatus_status',
127+
});
128+
});
129+
98130
it('updates markers on changes to beacons', () => {
99131
const room = setupRoom([defaultEvent]);
100132
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));

test/components/views/beacon/__snapshots__/OwnBeaconStatus-test.tsx.snap

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,13 @@ exports[`<OwnBeaconStatus /> renders without a beacon instance 1`] = `
55
displayStatus="Loading"
66
>
77
<BeaconStatus
8-
className="mx_MBeaconBody_chin"
98
displayLiveTimeRemaining={true}
109
displayStatus="Loading"
1110
label="Live location enabled"
12-
withIcon={true}
1311
>
1412
<div
15-
className="mx_BeaconStatus mx_BeaconStatus_Loading mx_MBeaconBody_chin"
13+
className="mx_BeaconStatus mx_BeaconStatus_Loading"
1614
>
17-
<StyledLiveBeaconIcon
18-
className="mx_BeaconStatus_icon"
19-
isIdle={true}
20-
withError={false}
21-
>
22-
<div
23-
className="mx_StyledLiveBeaconIcon mx_BeaconStatus_icon mx_StyledLiveBeaconIcon_idle"
24-
/>
25-
</StyledLiveBeaconIcon>
2615
<div
2716
className="mx_BeaconStatus_description"
2817
>

0 commit comments

Comments
 (0)