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

Commit 699a9ae

Browse files
author
Kerry
authored
LLS: expose way to enable live sharing labs flag from location dialog (#8416)
* add state for waiting for labs flag Signed-off-by: Kerry Archibald <[email protected]> * add enable live share component Signed-off-by: Kerry Archibald <[email protected]> * test enabling live share labs flag Signed-off-by: Kerry Archibald <[email protected]>
1 parent 4518011 commit 699a9ae

File tree

7 files changed

+330
-35
lines changed

7 files changed

+330
-35
lines changed

res/css/_components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
@import "./components/views/beacon/_OwnBeaconStatus.scss";
1515
@import "./components/views/beacon/_RoomLiveShareWarning.scss";
1616
@import "./components/views/beacon/_StyledLiveBeaconIcon.scss";
17+
@import "./components/views/location/_EnableLiveShare.scss";
1718
@import "./components/views/location/_LiveDurationDropdown.scss";
1819
@import "./components/views/location/_LocationShareMenu.scss";
1920
@import "./components/views/location/_MapError.scss";
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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_EnableLiveShare {
18+
flex: 1 1 0;
19+
display: flex;
20+
flex-direction: column;
21+
justify-content: flex-end;
22+
align-items: center;
23+
24+
padding: $spacing-32 $spacing-16;
25+
text-align: center;
26+
box-sizing: border-box;
27+
}
28+
29+
.mx_EnableLiveShare_heading {
30+
padding-top: $spacing-24;
31+
}
32+
33+
.mx_EnableLiveShare_icon {
34+
height: 58px;
35+
width: 58px;
36+
}
37+
38+
.mx_EnableLiveShare_description {
39+
padding: 0 $spacing-24;
40+
margin-bottom: $spacing-32;
41+
line-height: $font-20px;
42+
}
43+
44+
.mx_EnableLiveShare_button {
45+
margin-top: $spacing-32;
46+
height: 48px;
47+
width: 100%;
48+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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, { useState } from 'react';
18+
19+
import { _t } from '../../../languageHandler';
20+
import StyledLiveBeaconIcon from '../beacon/StyledLiveBeaconIcon';
21+
import AccessibleButton from '../elements/AccessibleButton';
22+
import LabelledToggleSwitch from '../elements/LabelledToggleSwitch';
23+
import Heading from '../typography/Heading';
24+
25+
interface Props {
26+
onSubmit: () => void;
27+
}
28+
29+
export const EnableLiveShare: React.FC<Props> = ({
30+
onSubmit,
31+
}) => {
32+
const [isEnabled, setEnabled] = useState(false);
33+
return (
34+
<div data-test-id='location-picker-enable-live-share' className='mx_EnableLiveShare'>
35+
<StyledLiveBeaconIcon className='mx_EnableLiveShare_icon' />
36+
<Heading className='mx_EnableLiveShare_heading' size='h3'>{ _t('Live location sharing') }</Heading>
37+
<p className='mx_EnableLiveShare_description'>
38+
{ _t(
39+
'Please note: this is a labs feature using a temporary implementation. ' +
40+
'This means you will not be able to delete your location history, ' +
41+
'and advanced users will be able to see your location history ' +
42+
'even after you stop sharing your live location with this room.',
43+
) }
44+
</p>
45+
<LabelledToggleSwitch
46+
data-test-id='enable-live-share-toggle'
47+
value={isEnabled}
48+
onChange={setEnabled}
49+
label={_t('Enable live location sharing')}
50+
/>
51+
<AccessibleButton
52+
data-test-id='enable-live-share-submit'
53+
className='mx_EnableLiveShare_button'
54+
element='button'
55+
kind='primary'
56+
onClick={onSubmit}
57+
disabled={!isEnabled}
58+
>
59+
{ _t('OK') }
60+
</AccessibleButton>
61+
</div>
62+
);
63+
};

src/components/views/location/LocationShareMenu.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import ShareDialogButtons from './ShareDialogButtons';
2727
import ShareType from './ShareType';
2828
import { LocationShareType } from './shareLocation';
2929
import { OwnProfileStore } from '../../../stores/OwnProfileStore';
30+
import { EnableLiveShare } from './EnableLiveShare';
31+
import { useFeatureEnabled } from '../../../hooks/useSettings';
32+
import { SettingLevel } from '../../../settings/SettingLevel';
3033

3134
type Props = Omit<ILocationPickerProps, 'onChoose' | 'shareType'> & {
3235
onFinished: (ev?: SyntheticEvent) => void;
@@ -37,11 +40,7 @@ type Props = Omit<ILocationPickerProps, 'onChoose' | 'shareType'> & {
3740
};
3841

3942
const getEnabledShareTypes = (): LocationShareType[] => {
40-
const enabledShareTypes = [LocationShareType.Own];
41-
42-
if (SettingsStore.getValue("feature_location_share_live")) {
43-
enabledShareTypes.push(LocationShareType.Live);
44-
}
43+
const enabledShareTypes = [LocationShareType.Own, LocationShareType.Live];
4544

4645
if (SettingsStore.getValue("feature_location_share_pin_drop")) {
4746
enabledShareTypes.push(LocationShareType.Pin);
@@ -60,6 +59,7 @@ const LocationShareMenu: React.FC<Props> = ({
6059
}) => {
6160
const matrixClient = useContext(MatrixClientContext);
6261
const enabledShareTypes = getEnabledShareTypes();
62+
const isLiveShareEnabled = useFeatureEnabled("feature_location_share_live");
6363

6464
const multipleShareTypesEnabled = enabledShareTypes.length > 1;
6565

@@ -73,19 +73,32 @@ const LocationShareMenu: React.FC<Props> = ({
7373
shareLiveLocation(matrixClient, roomId, displayName, openMenu) :
7474
shareLocation(matrixClient, roomId, shareType, relation, openMenu);
7575

76+
const onLiveShareEnableSubmit = () => {
77+
SettingsStore.setValue("feature_location_share_live", undefined, SettingLevel.DEVICE, true);
78+
};
79+
80+
const shouldAdvertiseLiveLabsFlag = shareType === LocationShareType.Live && !isLiveShareEnabled;
81+
7682
return <ContextMenu
7783
{...menuPosition}
7884
onFinished={onFinished}
7985
managed={false}
8086
>
8187
<div className="mx_LocationShareMenu">
82-
{ shareType ?
88+
{ shouldAdvertiseLiveLabsFlag &&
89+
<EnableLiveShare
90+
onSubmit={onLiveShareEnableSubmit}
91+
/>
92+
}
93+
{ !shouldAdvertiseLiveLabsFlag && !!shareType &&
8394
<LocationPicker
8495
sender={sender}
8596
shareType={shareType}
8697
onChoose={onLocationSubmit}
8798
onFinished={onFinished}
88-
/> :
99+
/>
100+
}
101+
{ !shareType &&
89102
<ShareType setShareType={setShareType} enabledShareTypes={enabledShareTypes} />
90103
}
91104
<ShareDialogButtons displayBack={!!shareType && multipleShareTypesEnabled} onBack={() => setShareType(undefined)} onCancel={onFinished} />

src/i18n/strings/en_EN.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,6 +2187,9 @@
21872187
"Submit logs": "Submit logs",
21882188
"Can't load this message": "Can't load this message",
21892189
"toggle event": "toggle event",
2190+
"Live location sharing": "Live location sharing",
2191+
"Please note: this is a labs feature using a temporary implementation. This means you will not be able to delete your location history, and advanced users will be able to see your location history even after you stop sharing your live location with this room.": "Please note: this is a labs feature using a temporary implementation. This means you will not be able to delete your location history, and advanced users will be able to see your location history even after you stop sharing your live location with this room.",
2192+
"Enable live location sharing": "Enable live location sharing",
21902193
"Share for %(duration)s": "Share for %(duration)s",
21912194
"Location": "Location",
21922195
"Could not fetch location": "Could not fetch location",

test/components/views/location/LocationShareMenu-test.tsx

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,28 @@ import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
3131
import { LocationShareType } from '../../../../src/components/views/location/shareLocation';
3232
import {
3333
findByTagAndTestId,
34-
flushPromises,
34+
findByTestId,
35+
flushPromisesWithFakeTimers,
3536
getMockClientWithEventEmitter,
3637
setupAsyncStoreWithClient,
3738
} from '../../../test-utils';
3839
import Modal from '../../../../src/Modal';
3940
import { DEFAULT_DURATION_MS } from '../../../../src/components/views/location/LiveDurationDropdown';
4041
import { OwnBeaconStore } from '../../../../src/stores/OwnBeaconStore';
42+
import { SettingLevel } from '../../../../src/settings/SettingLevel';
43+
44+
jest.useFakeTimers();
4145

4246
jest.mock('../../../../src/utils/location/findMapStyleUrl', () => ({
4347
findMapStyleUrl: jest.fn().mockReturnValue('test'),
4448
}));
4549

4650
jest.mock('../../../../src/settings/SettingsStore', () => ({
4751
getValue: jest.fn(),
52+
setValue: jest.fn(),
4853
monitorSetting: jest.fn(),
54+
watchSetting: jest.fn(),
55+
unwatchSetting: jest.fn(),
4956
}));
5057

5158
jest.mock('../../../../src/stores/OwnProfileStore', () => ({
@@ -115,6 +122,8 @@ describe('<LocationShareMenu />', () => {
115122
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient as unknown as MatrixClient);
116123
mocked(Modal).createTrackedDialog.mockClear();
117124

125+
jest.clearAllMocks();
126+
118127
await makeOwnBeaconStore();
119128
});
120129

@@ -150,15 +159,17 @@ describe('<LocationShareMenu />', () => {
150159
describe('when only Own share type is enabled', () => {
151160
beforeEach(() => enableSettings([]));
152161

153-
it('renders location picker when only Own share type is enabled', () => {
162+
it('renders own and live location options', () => {
154163
const component = getComponent();
155-
expect(component.find('ShareType').length).toBe(0);
156-
expect(component.find('LocationPicker').length).toBe(1);
164+
expect(getShareTypeOption(component, LocationShareType.Own).length).toBe(1);
165+
expect(getShareTypeOption(component, LocationShareType.Live).length).toBe(1);
157166
});
158167

159-
it('does not render back button when only Own share type is enabled', () => {
168+
it('renders back button from location picker screen', () => {
160169
const component = getComponent();
161-
expect(getBackButton(component).length).toBe(0);
170+
setShareType(component, LocationShareType.Own);
171+
172+
expect(getBackButton(component).length).toBe(1);
162173
});
163174

164175
it('clicking cancel button from location picker closes dialog', () => {
@@ -176,6 +187,8 @@ describe('<LocationShareMenu />', () => {
176187
const onFinished = jest.fn();
177188
const component = getComponent({ onFinished });
178189

190+
setShareType(component, LocationShareType.Own);
191+
179192
setLocation(component);
180193

181194
act(() => {
@@ -274,34 +287,88 @@ describe('<LocationShareMenu />', () => {
274287
});
275288
});
276289

277-
describe('with live location and pin drop enabled', () => {
278-
beforeEach(() => enableSettings([
279-
"feature_location_share_pin_drop",
280-
"feature_location_share_live",
281-
]));
290+
describe('with live location disabled', () => {
291+
beforeEach(() => enableSettings([]));
292+
293+
const getToggle = (component: ReactWrapper) =>
294+
findByTestId(component, 'enable-live-share-toggle').find('[role="switch"]').at(0);
295+
const getSubmitEnableButton = (component: ReactWrapper) =>
296+
findByTestId(component, 'enable-live-share-submit').at(0);
297+
298+
it('goes to labs flag screen after live options is clicked', () => {
299+
const onFinished = jest.fn();
300+
const component = getComponent({ onFinished });
282301

283-
it('renders share type switch with all 3 options', () => {
284-
// Given pin and live feature flags are enabled
285-
// When I click Location
302+
setShareType(component, LocationShareType.Live);
303+
304+
expect(findByTestId(component, 'location-picker-enable-live-share')).toMatchSnapshot();
305+
});
306+
307+
it('disables OK button when labs flag is not enabled', () => {
286308
const component = getComponent();
287309

288-
// The the Location picker is not visible yet
289-
expect(component.find('LocationPicker').length).toBe(0);
310+
setShareType(component, LocationShareType.Live);
290311

291-
// And all 3 buttons are visible on the LocationShare dialog
292-
expect(
293-
getShareTypeOption(component, LocationShareType.Own).length,
294-
).toBe(1);
312+
expect(getSubmitEnableButton(component).props()['disabled']).toBeTruthy();
313+
});
295314

296-
expect(
297-
getShareTypeOption(component, LocationShareType.Pin).length,
298-
).toBe(1);
315+
it('enables OK button when labs flag is toggled to enabled', () => {
316+
const component = getComponent();
317+
318+
setShareType(component, LocationShareType.Live);
299319

300-
const liveButton = getShareTypeOption(component, LocationShareType.Live);
301-
expect(liveButton.length).toBe(1);
320+
act(() => {
321+
getToggle(component).simulate('click');
322+
component.setProps({});
323+
});
324+
expect(getSubmitEnableButton(component).props()['disabled']).toBeFalsy();
325+
});
326+
327+
it('enables live share setting on ok button submit', () => {
328+
const component = getComponent();
329+
330+
setShareType(component, LocationShareType.Live);
331+
332+
act(() => {
333+
getToggle(component).simulate('click');
334+
component.setProps({});
335+
});
336+
337+
act(() => {
338+
getSubmitEnableButton(component).simulate('click');
339+
});
340+
341+
expect(SettingsStore.setValue).toHaveBeenCalledWith(
342+
'feature_location_share_live', undefined, SettingLevel.DEVICE, true,
343+
);
344+
});
345+
346+
it('navigates to location picker when live share is enabled in settings store', () => {
347+
// @ts-ignore
348+
mocked(SettingsStore.watchSetting).mockImplementation((featureName, roomId, callback) => {
349+
callback(featureName, roomId, SettingLevel.DEVICE, '', '');
350+
setTimeout(() => {
351+
callback(featureName, roomId, SettingLevel.DEVICE, '', '');
352+
}, 1000);
353+
});
354+
mocked(SettingsStore.getValue).mockReturnValue(false);
355+
const component = getComponent();
356+
357+
setShareType(component, LocationShareType.Live);
358+
359+
// we're on enable live share screen
360+
expect(findByTestId(component, 'location-picker-enable-live-share').length).toBeTruthy();
361+
362+
act(() => {
363+
mocked(SettingsStore.getValue).mockReturnValue(true);
364+
// advance so watchSetting will update the value
365+
jest.runAllTimers();
366+
});
367+
368+
component.setProps({});
302369

303-
// The live location button is enabled
304-
expect(liveButton.hasClass("mx_AccessibleButton_disabled")).toBeFalsy();
370+
// advanced to location picker
371+
expect(component.find('LocationPicker').length).toBeTruthy();
305372
});
306373
});
307374

@@ -351,7 +418,8 @@ describe('<LocationShareMenu />', () => {
351418
component.setProps({});
352419
});
353420

354-
await flushPromises();
421+
await flushPromisesWithFakeTimers();
422+
await flushPromisesWithFakeTimers();
355423

356424
expect(logSpy).toHaveBeenCalledWith("We couldn't start sharing your live location", error);
357425
expect(mocked(Modal).createTrackedDialog).toHaveBeenCalled();

0 commit comments

Comments
 (0)