Skip to content

Commit 71fe5fa

Browse files
chrisbobbegnprice
authored andcommitted
presence: Replace "unavailable" status with "invisible mode"
Fixes: #5445
1 parent 7d19edf commit 71fe5fa

File tree

7 files changed

+70
-14
lines changed

7 files changed

+70
-14
lines changed

src/account-info/ProfileScreen.js

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import ZulipButton from '../common/ZulipButton';
1414
import { logout } from '../account/logoutActions';
1515
import { tryStopNotifications } from '../notification/notifTokens';
1616
import AccountDetails from './AccountDetails';
17+
import { getRealm } from '../directSelectors';
1718
import { getOwnUser, getOwnUserId } from '../users/userSelectors';
18-
import { getAuth, getIdentity } from '../account/accountsSelectors';
19+
import { getAuth, getIdentity, getZulipFeatureLevel } from '../account/accountsSelectors';
1920
import { useNavigation } from '../react-navigation';
2021
import { showConfirmationDialog } from '../utils/info';
2122
import { OfflineNoticePlaceholder } from '../boot/OfflineNoticeProvider';
@@ -128,22 +129,37 @@ type Props = $ReadOnly<{|
128129
*/
129130
export default function ProfileScreen(props: Props): Node {
130131
const auth = useSelector(getAuth);
132+
const zulipFeatureLevel = useSelector(getZulipFeatureLevel);
131133
const ownUser = useSelector(getOwnUser);
132134
const ownUserId = useSelector(getOwnUserId);
135+
const presenceEnabled = useSelector(state => getRealm(state).presenceEnabled);
133136
const awayStatus = useSelector(state => getUserStatus(state, ownUserId).away);
134137

135138
return (
136139
<SafeAreaView mode="padding" edges={['top']} style={{ flex: 1 }}>
137140
<OfflineNoticePlaceholder />
138141
<ScrollView>
139142
<AccountDetails user={ownUser} showEmail={false} />
140-
<SwitchRow
141-
label="Set yourself to away"
142-
value={awayStatus}
143-
onValueChange={(away: boolean) => {
144-
api.updateUserStatus(auth, { away });
145-
}}
146-
/>
143+
{zulipFeatureLevel >= 148 ? (
144+
<SwitchRow
145+
label="Invisible mode"
146+
/* $FlowIgnore[incompatible-cast] - Only null when FL is <89;
147+
see comment on RealmState['presenceEnabled'] */
148+
value={!(presenceEnabled: boolean)}
149+
onValueChange={(newValue: boolean) => {
150+
api.updateUserSettings(auth, { presence_enabled: !newValue }, zulipFeatureLevel);
151+
}}
152+
/>
153+
) : (
154+
// TODO(server-6.0): Remove.
155+
<SwitchRow
156+
label="Set yourself to away"
157+
value={awayStatus}
158+
onValueChange={(away: boolean) => {
159+
api.updateUserStatus(auth, { away });
160+
}}
161+
/>
162+
)}
147163
<View style={styles.buttonRow}>
148164
<SetStatusButton />
149165
</View>

src/api/modelTypes.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,11 @@ export type UserGroup = $ReadOnly<{|
446446
*/
447447
export type UserStatus = $ReadOnly<{|
448448
// true/false: User unavailable/available.
449+
//
450+
// Deprecated: "invisible mode", new in FL 148, doesn't involve this:
451+
// https://chat.zulip.org/#narrow/stream/2-general/topic/.22unavailable.22.20status/near/1454779
452+
// Don't use it except for servers <148.
453+
// TODO(server-6.0): Remove.
449454
away: boolean,
450455

451456
// "foo": User's status text is "foo".

src/common/PresenceStatusIndicator.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ function PresenceStatusIndicatorOffline() {
9696
return <View style={[styles.offline, styles.common]} />;
9797
}
9898

99+
// TODO(server-6.0): Remove; this status was made obsolete in FL 148.
99100
function PresenceStatusIndicatorUnavailable() {
100101
return (
101102
<View style={[styles.unavailableWrapper, styles.common]}>

src/title/ActivityText.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { TextStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet
66

77
import type { UserOrBot } from '../types';
88
import { useSelector } from '../react-redux';
9-
import { getPresence } from '../selectors';
9+
import { getPresence, getZulipFeatureLevel } from '../selectors';
1010
import { getUserStatus } from '../user-statuses/userStatusesModel';
1111
import { presenceToHumanTime } from '../utils/presence';
1212
import ZulipText from '../common/ZulipText';
@@ -18,14 +18,16 @@ type Props = $ReadOnly<{|
1818

1919
export default function ActivityText(props: Props): Node {
2020
const { style } = props;
21+
22+
const zulipFeatureLevel = useSelector(getZulipFeatureLevel);
2123
const presence = useSelector(state => getPresence(state)[props.user.email]);
2224
const userStatus = useSelector(state => getUserStatus(state, props.user.user_id));
2325

2426
if (!presence) {
2527
return null;
2628
}
2729

28-
const activity = presenceToHumanTime(presence, userStatus);
30+
const activity = presenceToHumanTime(presence, userStatus, zulipFeatureLevel);
2931

3032
return <ZulipText style={style} text={`Active ${activity}`} />;
3133
}

src/utils/__tests__/presence-test.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
statusFromPresence,
77
statusFromPresenceAndUserStatus,
88
} from '../presence';
9+
import * as eg from '../../__tests__/lib/exampleData';
910

1011
const currentTimestamp = Date.now() / 1000;
1112

@@ -87,6 +88,7 @@ describe('presenceToHumanTime', () => {
8788
aggregated: { client: 'website', status: 'active', timestamp: currentTimestamp - 240 },
8889
},
8990
{ away: false, status_text: null, status_emoji: null },
91+
eg.recentZulipFeatureLevel,
9092
),
9193
).toBe('4 minutes ago');
9294
});
@@ -98,18 +100,33 @@ describe('presenceToHumanTime', () => {
98100
aggregated: { client: 'website', status: 'active', timestamp: currentTimestamp - 100 },
99101
},
100102
{ away: false, status_text: null, status_emoji: null },
103+
eg.recentZulipFeatureLevel,
101104
),
102105
).toBe('now');
103106
});
104107

105-
test('if less than a day, and the user is "away" the user is ...', () => {
108+
// TODO(server-6.0): Remove
109+
test('Pre-FL 148: if less than a day and user is "away", use imprecise "today"', () => {
106110
expect(
107111
presenceToHumanTime(
108112
{ aggregated: { client: 'website', status: 'active', timestamp: currentTimestamp - 100 } },
109113
{ away: true, status_text: null, status_emoji: null },
114+
147,
110115
),
111116
).toBe('today');
112117
});
118+
119+
// TODO(server-6.0): Remove, once this test case is redundant with those
120+
// above after the status parameter is gone.
121+
test('FL 148: if less than a day and user is "away", *don\'t* use imprecise "today"', () => {
122+
expect(
123+
presenceToHumanTime(
124+
{ aggregated: { client: 'website', status: 'active', timestamp: currentTimestamp - 100 } },
125+
{ away: true, status_text: null, status_emoji: null },
126+
148,
127+
),
128+
).not.toBe('today');
129+
});
113130
});
114131

115132
describe('statusFromPresence', () => {

src/utils/presence.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { UserId } from '../api/idTypes';
1111
import { getPresence } from '../directSelectors';
1212
import { tryGetUserForId } from '../users/userSelectors';
1313
import { getUserStatus } from '../user-statuses/userStatusesModel';
14+
import { getZulipFeatureLevel } from '../account/accountsSelectors';
1415

1516
/** The relation `>=`, where `active` > `idle` > `offline`. */
1617
const presenceStatusGeq = (a: PresenceStatus, b: PresenceStatus): boolean => {
@@ -81,14 +82,21 @@ export const getAggregatedPresence = (presence: UserPresence): ClientPresence =>
8182
return { client, status, timestamp };
8283
};
8384

84-
export const presenceToHumanTime = (presence: UserPresence, status: UserStatus): string => {
85+
export const presenceToHumanTime = (
86+
presence: UserPresence,
87+
status: UserStatus,
88+
zulipFeatureLevel: number,
89+
): string => {
8590
if (!presence || !presence.aggregated) {
8691
return 'never';
8792
}
8893

8994
const lastTimeActive = new Date(presence.aggregated.timestamp * 1000);
9095

91-
if (status.away && differenceInDays(Date.now(), lastTimeActive) < 1) {
96+
// "Invisible mode", new in FL 148, doesn't involve UserStatus['away']:
97+
// https://chat.zulip.org/#narrow/stream/2-general/topic/.22unavailable.22.20status/near/1454779
98+
// TODO(server-6.0): Remove this `if` block and the `status` parameter.
99+
if (zulipFeatureLevel < 148 && status.away && differenceInDays(Date.now(), lastTimeActive) < 1) {
92100
// Be vague when an unavailable user is recently online.
93101
// TODO: This phrasing doesn't really match the logic and can be misleading.
94102
return 'today';
@@ -118,6 +126,7 @@ export const statusFromPresence = (presence: UserPresence | void): PresenceStatu
118126
return presence.aggregated.status;
119127
};
120128

129+
// TODO(server-6.0): Remove; UserStatus['away'] was deprecated at FL 148.
121130
export const statusFromPresenceAndUserStatus = (
122131
presence: UserPresence | void,
123132
userStatus: UserStatus,
@@ -145,5 +154,10 @@ export const getPresenceStatusForUserId = (
145154
}
146155
const userStatus = getUserStatus(state, userId);
147156

148-
return statusFromPresenceAndUserStatus(userPresence, userStatus);
157+
// "Invisible mode", new in FL 148, doesn't involve UserStatus['away']:
158+
// https://chat.zulip.org/#narrow/stream/2-general/topic/.22unavailable.22.20status/near/1454779
159+
// TODO(server-6.0): Simplify to just statusFromPresence.
160+
return getZulipFeatureLevel(state) >= 148
161+
? statusFromPresence(userPresence)
162+
: statusFromPresenceAndUserStatus(userPresence, userStatus);
149163
};

static/translations/messages_en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"Invisible mode": "Invisible mode",
23
"{num_of_people, plural,\n one {This message has been <z-link>read</z-link> by {num_of_people} person:}\n other {This message has been <z-link>read</z-link> by {num_of_people} people:}}": "{num_of_people, plural,\n one {This message has been <z-link>read</z-link> by {num_of_people} person:}\n other {This message has been <z-link>read</z-link> by {num_of_people} people:}}",
34
"View read receipts": "View read receipts",
45
"Failed to show read receipts": "Failed to show read receipts",

0 commit comments

Comments
 (0)