Skip to content

Commit 6e88153

Browse files
committed
settings: Add option to mark read on scroll only in conversation views
Using the handy new InputRowRadioButtons component we added in edf5322. Fixes: #5241
1 parent f39fcad commit 6e88153

File tree

10 files changed

+103
-19
lines changed

10 files changed

+103
-19
lines changed

src/common/InputRowRadioButtons.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ type Props<TItemKey> = $ReadOnly<{|
5858
|}>;
5959

6060
/**
61-
* A form-input row for the user to make a choice, radio-button style.
61+
* An input row for the user to make a choice, radio-button style.
6262
*
6363
* Shows the current value (the selected item), represented as the item's
6464
* `.title`. When tapped, opens the selectable-options screen, where the

src/common/SwitchRow.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ type Props = $ReadOnly<{|
1818
const componentStyles = createStyleSheet({
1919
container: {
2020
// For uniformity with other rows this might share a screen with, e.g.,
21-
// NestedNavRow on the settings screen. See height-related attributes on
22-
// those rows.
21+
// NestedNavRow and InputRowRadioButtons on the settings screen. See
22+
// height-related attributes on those rows.
2323
minHeight: 48,
2424
},
2525
icon: {

src/reduxTypes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ export type GlobalSettingsState = $ReadOnly<{
397397

398398
// Possibly this should be per-account. If so it should probably be put
399399
// on the server, so it can also be cross-device for the account.
400-
doNotMarkMessagesAsRead: boolean,
400+
markMessagesReadOnScroll: 'always' | 'never' | 'conversation-views-only',
401401

402402
...
403403
}>;

src/settings/SettingsScreen.js

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { AppNavigationProp } from '../nav/AppNavigator';
88
import { useGlobalSelector, useDispatch } from '../react-redux';
99
import { getGlobalSettings } from '../selectors';
1010
import NestedNavRow from '../common/NestedNavRow';
11+
import InputRowRadioButtons from '../common/InputRowRadioButtons';
1112
import SwitchRow from '../common/SwitchRow';
1213
import Screen from '../common/Screen';
1314
import {
@@ -27,8 +28,8 @@ type Props = $ReadOnly<{|
2728
export default function SettingsScreen(props: Props): Node {
2829
const theme = useGlobalSelector(state => getGlobalSettings(state).theme);
2930
const browser = useGlobalSelector(state => getGlobalSettings(state).browser);
30-
const doNotMarkMessagesAsRead = useGlobalSelector(
31-
state => getGlobalSettings(state).doNotMarkMessagesAsRead,
31+
const markMessagesReadOnScroll = useGlobalSelector(
32+
state => getGlobalSettings(state).markMessagesReadOnScroll,
3233
);
3334
const dispatch = useDispatch();
3435
const { navigation } = props;
@@ -47,11 +48,23 @@ export default function SettingsScreen(props: Props): Node {
4748
dispatch(setGlobalSettings({ browser: value ? 'embedded' : 'external' }));
4849
}}
4950
/>
50-
<SwitchRow
51-
label="Do not mark messages read on scroll"
52-
value={doNotMarkMessagesAsRead}
51+
<InputRowRadioButtons
52+
navigation={navigation}
53+
label="Mark messages as read on scroll"
54+
description="When scrolling through messages, should they automatically be marked as read?"
55+
valueKey={markMessagesReadOnScroll}
56+
items={[
57+
{ key: 'always', title: 'Always' },
58+
{ key: 'never', title: 'Never' },
59+
{
60+
key: 'conversation-views-only',
61+
title: 'Only in conversation views',
62+
subtitle:
63+
'Messages will be marked as read in single-topic or private-message views, but not in interleaved views, such as whole-stream views.',
64+
},
65+
]}
5366
onValueChange={value => {
54-
dispatch(setGlobalSettings({ doNotMarkMessagesAsRead: value }));
67+
dispatch(setGlobalSettings({ markMessagesReadOnScroll: value }));
5568
}}
5669
/>
5770
<NestedNavRow

src/settings/settingsReducer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const initialState: SettingsState = {
1818
theme: 'default',
1919
browser: 'default',
2020
experimentalFeaturesEnabled: false,
21-
doNotMarkMessagesAsRead: false,
21+
markMessagesReadOnScroll: 'always',
2222

2323
//
2424
// PerAccountSettingsState

src/storage/__tests__/migrations-test.js

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,27 @@ describe('migrations', () => {
8484
},
8585
};
8686

87+
// What `base` becomes after migrations up through 52.
88+
const base52 = {
89+
...base37,
90+
migrations: { version: 52 },
91+
settings: {
92+
language: 'en',
93+
theme: 'default',
94+
offlineNotification: true,
95+
onlineNotification: true,
96+
experimentalFeaturesEnabled: false,
97+
streamNotification: false,
98+
browser: 'default',
99+
// renamed/retyped from boolean doNotMarkMessagesAsRead
100+
markMessagesReadOnScroll: 'always',
101+
},
102+
};
103+
87104
// What `base` becomes after all migrations.
88105
const endBase = {
89-
...base37,
90-
migrations: { version: 51 },
106+
...base52,
107+
migrations: { version: 52 },
91108
};
92109

93110
for (const [desc, before, after] of [
@@ -206,12 +223,12 @@ describe('migrations', () => {
206223
[
207224
'check 37 with setting already false',
208225
{ ...base15, settings: { ...base15.settings, doNotMarkMessagesAsRead: false } },
209-
{ ...endBase, settings: { ...endBase.settings, doNotMarkMessagesAsRead: false } },
226+
{ ...endBase, settings: { ...endBase.settings, markMessagesReadOnScroll: 'always' } },
210227
],
211228
[
212229
'check 37 with setting already true',
213230
{ ...base15, settings: { ...base15.settings, doNotMarkMessagesAsRead: true } },
214-
{ ...endBase, settings: { ...endBase.settings, doNotMarkMessagesAsRead: true } },
231+
{ ...endBase, settings: { ...endBase.settings, markMessagesReadOnScroll: 'never' } },
215232
],
216233
[
217234
'check 38',
@@ -241,6 +258,24 @@ describe('migrations', () => {
241258
drafts: { 'topic:8:stuff': 'text', 'stream:8': 'more text', 'pm:12': 'pm text' },
242259
},
243260
],
261+
[
262+
'check 52 with old setting false',
263+
{
264+
...base37,
265+
migrations: { version: 51 },
266+
settings: { ...base37.settings, doNotMarkMessagesAsRead: false },
267+
},
268+
{ ...endBase, settings: { ...endBase.settings, markMessagesReadOnScroll: 'always' } },
269+
],
270+
[
271+
'check 52 with old setting true',
272+
{
273+
...base37,
274+
migrations: { version: 51 },
275+
settings: { ...base37.settings, doNotMarkMessagesAsRead: true },
276+
},
277+
{ ...endBase, settings: { ...endBase.settings, markMessagesReadOnScroll: 'never' } },
278+
],
244279
]) {
245280
/* eslint-disable no-loop-func */
246281
test(desc, async () => {

src/storage/migrations.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,12 +371,16 @@ const migrationsInner: {| [string]: (LessPartialState) => LessPartialState |} =
371371
// Handle explicitly the migrations above (before 30, 34, 36, and this
372372
// one) that were done implicitly by the behavior of `autoRehydrate` on a
373373
// REHYDRATE action.
374+
/* $FlowIgnore[prop-missing]: `doNotMarkMessagesAsRead` renamed to
375+
`markMessagesReadOnScroll` in 52 */
374376
'37': state => ({
375377
// This handles the migrations affecting RealmState, before 34, 36, and here.
376378
...dropCache(state),
377379
// Handle the migration before 30.
378380
settings: {
379381
...state.settings,
382+
/* $FlowIgnore[prop-missing]: `doNotMarkMessagesAsRead` renamed to
383+
`markMessagesReadOnScroll` in 52 */
380384
doNotMarkMessagesAsRead: state.settings.doNotMarkMessagesAsRead ?? false,
381385
},
382386
}),
@@ -443,6 +447,19 @@ const migrationsInner: {| [string]: (LessPartialState) => LessPartialState |} =
443447
// Add serverEmojiData to state.realm.
444448
'51': dropCache,
445449

450+
// Change boolean doNotMarkMessagesAsRead to enum markMessagesReadOnScroll
451+
'52': state => {
452+
// $FlowIgnore[prop-missing]
453+
const { doNotMarkMessagesAsRead, ...restSettings } = state.settings;
454+
return {
455+
...state,
456+
settings: {
457+
...restSettings,
458+
markMessagesReadOnScroll: (doNotMarkMessagesAsRead: boolean) ? 'never' : 'always',
459+
},
460+
};
461+
},
462+
446463
// TIP: When adding a migration, consider just using `dropCache`.
447464
// (See its jsdoc for guidance on when that's the right answer.)
448465
};

src/webview/MessageList.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ import { handleWebViewOutboundEvent } from './handleOutboundEvents';
3535
import { base64Utf8Encode } from '../utils/encoding';
3636
import * as logging from '../utils/logging';
3737
import { tryParseUrl } from '../utils/url';
38-
import { caseNarrow } from '../utils/narrow';
38+
import { caseNarrow, isConversationNarrow } from '../utils/narrow';
3939
import { type BackgroundData, getBackgroundData } from './backgroundData';
40+
import { ensureUnreachable } from '../generics';
4041

4142
type OuterProps = $ReadOnly<{|
4243
narrow: Narrow,
@@ -300,7 +301,20 @@ const MessageList: ComponentType<OuterProps> = connect<SelectorProps, _, _>(
300301
),
301302
typingUsers: getCurrentTypingUsers(state, props.narrow),
302303
doNotMarkMessagesAsRead:
303-
!marksMessagesAsRead(props.narrow) || globalSettings.doNotMarkMessagesAsRead,
304+
!marksMessagesAsRead(props.narrow)
305+
|| (() => {
306+
switch (globalSettings.markMessagesReadOnScroll) {
307+
case 'always':
308+
return false;
309+
case 'never':
310+
return true;
311+
case 'conversation-views-only':
312+
return !isConversationNarrow(props.narrow);
313+
default:
314+
ensureUnreachable(globalSettings.markMessagesReadOnScroll);
315+
return false;
316+
}
317+
})(),
304318
};
305319
},
306320
)(connectActionSheet(withGetText(MessageListInner)));

src/webview/__tests__/generateInboundEvents-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('generateInboundEvents', () => {
1515
messages: [],
1616
messageListElementsForShownMessages: [],
1717
typingUsers: [],
18-
doNotMarkMessagesAsRead: eg.baseReduxState.settings.doNotMarkMessagesAsRead,
18+
doNotMarkMessagesAsRead: false,
1919
});
2020

2121
type FudgedProps = {|

static/translations/messages_en.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,12 @@
283283
"Active": "Active",
284284
"Idle": "Idle",
285285
"Offline": "Offline",
286-
"Do not mark messages read on scroll": "Do not mark messages read on scroll",
286+
"Mark messages as read on scroll": "Mark messages as read on scroll",
287+
"When scrolling through messages, should they automatically be marked as read?": "When scrolling through messages, should they automatically be marked as read?",
288+
"Always": "Always",
289+
"Never": "Never",
290+
"Only in conversation views": "Only in conversation views",
291+
"Messages will be marked as read in single-topic or private-message views, but not in interleaved views, such as whole-stream views.": "Messages will be marked as read in single-topic or private-message views, but not in interleaved views, such as whole-stream views.",
287292
"Topics": "Topics",
288293
"Add subscribers": "Add subscribers",
289294
"Subscribe": "Subscribe",

0 commit comments

Comments
 (0)