Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@react-native-community/masked-view": "^0.1.10",
"@react-native-community/netinfo": "6.0.0",
"@react-native-community/push-notification-ios": "^1.5.0",
"@react-native-picker/picker": "^2.4.1",
"@react-navigation/bottom-tabs": "npm:@zulip/react-navigation-bottom-tabs@5.11.16-0.zulip.1",
"@react-navigation/drawer": "^5.9.3",
"@react-navigation/material-top-tabs": "^5.2.19",
Expand Down
2 changes: 1 addition & 1 deletion src/reduxTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ export type GlobalSettingsState = $ReadOnly<{

// Possibly this should be per-account. If so it should probably be put
// on the server, so it can also be cross-device for the account.
doNotMarkMessagesAsRead: boolean,
shouldMarkAsReadOnScroll: 'never' | 'always' | 'conversation',

...
}>;
Expand Down
40 changes: 31 additions & 9 deletions src/settings/SettingsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

import React, { useCallback } from 'react';
import type { Node } from 'react';

import { View } from 'react-native';
// $FlowFixMe[untyped-import]
import { Picker } from '@react-native-picker/picker';
import type { RouteProp } from '../react-navigation';
import type { MainTabsNavigationProp } from '../main/MainTabsScreen';
import * as NavigationService from '../nav/NavigationService';
import { useGlobalSelector, useDispatch } from '../react-redux';
import { getGlobalSettings } from '../selectors';
import NestedNavRow from '../common/NestedNavRow';
import SwitchRow from '../common/SwitchRow';
import ZulipTextIntl from '../common/ZulipTextIntl';
import Screen from '../common/Screen';
import {
IconDiagnostics,
Expand All @@ -34,8 +37,8 @@ type Props = $ReadOnly<{|
export default function SettingsScreen(props: Props): Node {
const theme = useGlobalSelector(state => getGlobalSettings(state).theme);
const browser = useGlobalSelector(state => getGlobalSettings(state).browser);
const doNotMarkMessagesAsRead = useGlobalSelector(
state => getGlobalSettings(state).doNotMarkMessagesAsRead,
const shouldMarkAsReadOnScroll = useGlobalSelector(
state => getGlobalSettings(state).shouldMarkAsReadOnScroll,
);
const dispatch = useDispatch();

Expand All @@ -53,13 +56,32 @@ export default function SettingsScreen(props: Props): Node {
dispatch(setGlobalSettings({ browser: value ? 'embedded' : 'external' }));
}}
/>
<SwitchRow
label="Do not mark messages read on scroll"
value={doNotMarkMessagesAsRead}
onValueChange={value => {
dispatch(setGlobalSettings({ doNotMarkMessagesAsRead: value }));
<View
style={{
flex: 1,
flexDirection: 'row',
alignContent: 'space-around',
flexWrap: 'wrap',
paddingLeft: 15,
}}
/>
>
<ZulipTextIntl
style={{ fontSize: 15, alignSelf: 'center' }}
text="Mark as read on scroll"
/>
<Picker
selectedValue={shouldMarkAsReadOnScroll}
onValueChange={(itemValue, itemIndex) => {
dispatch(setGlobalSettings({ shouldMarkAsReadOnScroll: itemValue }));
}}
style={{ height: '100%', width: '50%' }}
>
<Picker.Item label="Always" value="always" />
<Picker.Item label="Conversation" value="conversation" />
<Picker.Item label="Never" value="never" />
</Picker>
</View>

<NestedNavRow
Icon={IconNotifications}
label="Notifications"
Expand Down
2 changes: 1 addition & 1 deletion src/settings/settingsReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const initialState: SettingsState = {
experimentalFeaturesEnabled: false,
streamNotification: false,
browser: 'default',
doNotMarkMessagesAsRead: false,
shouldMarkAsReadOnScroll: 'always',
};

export default (state: SettingsState = initialState, action: Action): SettingsState => {
Expand Down
19 changes: 12 additions & 7 deletions src/storage/__tests__/migrations-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe('migrations', () => {
streamNotification: false,
// added:
browser: 'default',
doNotMarkMessagesAsRead: false,
shouldMarkAsReadOnScroll: 'always',
},
};

Expand Down Expand Up @@ -204,14 +204,19 @@ describe('migrations', () => {
],
// 36 covered by whole
[
'check 37 with setting already false',
{ ...base15, settings: { ...base15.settings, doNotMarkMessagesAsRead: false } },
{ ...endBase, settings: { ...endBase.settings, doNotMarkMessagesAsRead: false } },
'check 37 with setting - always',
{ ...base15, settings: { ...base15.settings, shouldMarkAsReadOnScroll: 'always' } },
{ ...endBase, settings: { ...endBase.settings, shouldMarkAsReadOnScroll: 'always' } },
],
[
'check 37 with setting already true',
{ ...base15, settings: { ...base15.settings, doNotMarkMessagesAsRead: true } },
{ ...endBase, settings: { ...endBase.settings, doNotMarkMessagesAsRead: true } },
'check 37 with setting - conversation',
{ ...base15, settings: { ...base15.settings, shouldMarkAsReadOnScroll: 'conversation' } },
{ ...endBase, settings: { ...endBase.settings, shouldMarkAsReadOnScroll: 'conversation' } },
],
[
'check 37 with setting - never',
{ ...base15, settings: { ...base15.settings, shouldMarkAsReadOnScroll: 'never' } },
{ ...endBase, settings: { ...endBase.settings, shouldMarkAsReadOnScroll: 'never' } },
],
[
'check 38',
Expand Down
2 changes: 1 addition & 1 deletion src/storage/migrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ const migrationsInner: {| [string]: (LessPartialState) => LessPartialState |} =
// Handle the migration before 30.
settings: {
...state.settings,
doNotMarkMessagesAsRead: state.settings.doNotMarkMessagesAsRead ?? false,
shouldMarkAsReadOnScroll: state.settings.shouldMarkAsReadOnScroll ?? 'always',
},
}),

Expand Down
32 changes: 18 additions & 14 deletions src/webview/MessageList.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type SelectorProps = {|
fetching: Fetching,
messageListElementsForShownMessages: $ReadOnlyArray<MessageListElement>,
typingUsers: $ReadOnlyArray<UserOrBot>,
doNotMarkMessagesAsRead: boolean,
shouldMarkAsReadOnScroll: 'never' | 'always' | 'conversation',
|};

export type Props = $ReadOnly<{|
Expand Down Expand Up @@ -158,7 +158,7 @@ class MessageListInner extends Component<Props> {
messageListElementsForShownMessages,
initialScrollMessageId,
showMessagePlaceholders,
doNotMarkMessagesAsRead,
shouldMarkAsReadOnScroll,
_,
} = this.props;
const contentHtml = messageListElementsForShownMessages
Expand All @@ -175,7 +175,7 @@ class MessageListInner extends Component<Props> {
scrollMessageId: initialScrollMessageId,
auth,
showMessagePlaceholders,
doNotMarkMessagesAsRead,
shouldMarkAsReadOnScroll,
});

/**
Expand Down Expand Up @@ -261,29 +261,32 @@ class MessageListInner extends Component<Props> {
* "Can", not "will": other conditions can mean we don't want to mark
* messages as read even when in a narrow where this is true.
*/
const marksMessagesAsRead = (narrow: Narrow): boolean =>
const marksMessagesAsRead = (
narrow: Narrow,
shouldMarkAsReadOnScroll: 'never' | 'always' | 'conversation',
): 'never' | 'always' | 'conversation' =>
Comment on lines +264 to +267
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except for the search, starred, and mentioned narrows, every other narrows should depend on the currently chosen setting. That's why I added this extra parameter

// Generally we want these to agree with the web/desktop app, so that user
// expectations transfer between the different apps.
caseNarrow(narrow, {
// These narrows show one conversation in full. Each message appears
// in its full context, so it makes sense to say the user's read it
// and doesn't need to be shown it as unread again.
topic: () => true,
pm: () => true,
topic: () => shouldMarkAsReadOnScroll,
pm: () => shouldMarkAsReadOnScroll,

// These narrows show several conversations interleaved. They always
// show entire conversations, so each message still appears in its
// full context and it still makes sense to mark it as read.
stream: () => true,
home: () => true,
allPrivate: () => true,
stream: () => shouldMarkAsReadOnScroll,
home: () => shouldMarkAsReadOnScroll,
allPrivate: () => shouldMarkAsReadOnScroll,

// These narrows show selected messages out of context. The user will
// typically still want to see them another way, in context, before
// letting them disappear from their list of unread messages.
search: () => false,
starred: () => false,
mentioned: () => false,
search: () => 'never',
starred: () => 'never',
mentioned: () => 'never',
});

const MessageList: ComponentType<OuterProps> = connect<SelectorProps, _, _>(
Expand All @@ -303,8 +306,9 @@ const MessageList: ComponentType<OuterProps> = connect<SelectorProps, _, _>(
props.narrow,
),
typingUsers: getCurrentTypingUsers(state, props.narrow),
doNotMarkMessagesAsRead:
!marksMessagesAsRead(props.narrow) || globalSettings.doNotMarkMessagesAsRead,
shouldMarkAsReadOnScroll:
marksMessagesAsRead(props.narrow, globalSettings.shouldMarkAsReadOnScroll)
|| globalSettings.shouldMarkAsReadOnScroll,
};
},
)(connectActionSheet(withGetText(MessageListInner)));
Expand Down
2 changes: 1 addition & 1 deletion src/webview/__tests__/generateInboundEvents-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('generateInboundEvents', () => {
messages: [],
messageListElementsForShownMessages: [],
typingUsers: [],
doNotMarkMessagesAsRead: eg.baseReduxState.settings.doNotMarkMessagesAsRead,
shouldMarkAsReadOnScroll: eg.baseReduxState.settings.shouldMarkAsReadOnScroll,
});

type FudgedProps = {|
Expand Down
13 changes: 10 additions & 3 deletions src/webview/handleOutboundEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { pmKeyRecipientsFromMessage } from '../utils/recipient';
import { isUrlAnImage } from '../utils/url';
import * as logging from '../utils/logging';
import { filterUnreadMessagesInRange } from '../utils/unread';
import { parseNarrow } from '../utils/narrow';
import { parseNarrow, isTopicNarrow, isPmNarrow } from '../utils/narrow';
import {
fetchOlder,
fetchNewer,
Expand Down Expand Up @@ -166,7 +166,7 @@ type Props = $ReadOnly<{
dispatch: Dispatch,
messages: $ReadOnlyArray<Message | Outbox>,
narrow: Narrow,
doNotMarkMessagesAsRead: boolean,
shouldMarkAsReadOnScroll: 'never' | 'always' | 'conversation',
showActionSheetWithOptions: ShowActionSheetWithOptions,
startEditMessage: (editMessage: EditMessage) => void,
...
Expand All @@ -185,7 +185,14 @@ const fetchMore = (props: Props, event: WebViewOutboundEventScroll) => {

const markRead = (props: Props, event: WebViewOutboundEventScroll) => {
const { flags, auth } = props.backgroundData;
if (props.doNotMarkMessagesAsRead) {
if (props.shouldMarkAsReadOnScroll === 'never') {
return;
}
if (
props.shouldMarkAsReadOnScroll === 'conversation'
&& !isTopicNarrow(props.narrow)
&& !isPmNarrow(props.narrow)
) {
return;
}
const unreadMessageIds = filterUnreadMessagesInRange(
Expand Down
4 changes: 2 additions & 2 deletions src/webview/html/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type InitOptionsType = {|
scrollMessageId: number | null,
auth: Auth,
showMessagePlaceholders: boolean,
doNotMarkMessagesAsRead: boolean,
shouldMarkAsReadOnScroll: 'never' | 'always' | 'conversation',
|};

/**
Expand Down Expand Up @@ -45,7 +45,7 @@ export default (
theme: ThemeName,
initOptions: InitOptionsType,
): string => template`
$!${script(initOptions.scrollMessageId, initOptions.auth, initOptions.doNotMarkMessagesAsRead)}
$!${script(initOptions.scrollMessageId, initOptions.auth, initOptions.shouldMarkAsReadOnScroll)}
$!${css(theme)}

<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
Expand Down
2 changes: 1 addition & 1 deletion src/webview/js/generatedEs3.js
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ var compiledWebviewJs = (function (exports) {
endMessageId: rangeHull.last
});

if (!doNotMarkMessagesAsRead) {
if (shouldMarkAsReadOnScroll === 'always' || shouldMarkAsReadOnScroll === 'conversation') {
setMessagesReadAttributes(rangeHull);
}

Expand Down
4 changes: 2 additions & 2 deletions src/webview/js/js.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ declare var isDevelopment: boolean;
* defined in `handleInitialLoad`.
* declared globally so as to use across functions.
*/
declare var doNotMarkMessagesAsRead: boolean;
declare var shouldMarkAsReadOnScroll: 'never' | 'always' | 'conversation';

// We pull out document.body in one place, and check it's not null, in order
// to provide that assertion to the type-checker.
Expand Down Expand Up @@ -471,7 +471,7 @@ const sendScrollMessage = () => {
startMessageId: rangeHull.first,
endMessageId: rangeHull.last,
});
if (!doNotMarkMessagesAsRead) {
if (shouldMarkAsReadOnScroll === 'always' || shouldMarkAsReadOnScroll === 'conversation') {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conditional is not entirely correct.

setMessagesReadAttributes(rangeHull), should only work if the chosen setting is 'always' or the settings is 'conversation' and the current narrow is a topic or pm.

Is there a way to check which narrow we are currently in the js.js file?

setMessagesReadAttributes(rangeHull);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like the place where the setting actually takes effect; is that right? It seems like this bit should be replaced with something different, instead of removed altogether.

// If there are no visible + read messages (for instance, the entire screen
Expand Down
4 changes: 2 additions & 2 deletions src/webview/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import compiledWebviewJs from './generatedEs3';
export default (
scrollMessageId: number | null,
auth: Auth,
doNotMarkMessagesAsRead: boolean,
shouldMarkAsReadOnScroll: 'never' | 'always' | 'conversation',
): string => `
<script>
window.__forceSmoothScrollPolyfill__ = true;
Expand All @@ -24,7 +24,7 @@ ${matchesPolyfill}
document.addEventListener('DOMContentLoaded', function() {
var platformOS = ${JSON.stringify(Platform.OS)};
var isDevelopment = ${JSON.stringify(process.env.NODE_ENV === 'development')};
var doNotMarkMessagesAsRead = ${JSON.stringify(doNotMarkMessagesAsRead)};
var shouldMarkAsReadOnScroll = ${JSON.stringify(shouldMarkAsReadOnScroll)};
${compiledWebviewJs}
compiledWebviewJs.handleInitialLoad(
${JSON.stringify(scrollMessageId)},
Expand Down
1 change: 1 addition & 0 deletions static/translations/messages_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
"Settings": "Settings",
"Night mode": "Night mode",
"Open links with in-app browser": "Open links with in-app browser",
"Mark as read on scroll": "Mark as read on scroll",
"Language": "Language",
"Arabic": "Arabic",
"Bokmål": "Bokmål",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1967,6 +1967,11 @@
dependencies:
invariant "^2.2.4"

"@react-native-picker/picker@^2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@react-native-picker/picker/-/picker-2.4.1.tgz#92feb7e0672d739624517dae04bf4de1452dfcdc"
integrity sha512-1XWy3IQgwr7MWd30KdY1iUh2gQZD+JiotN1ifj/ptFUYKon/0UFwngKQaWCO/CP/FdLl20/huSSLwKedYrdMMA==

"@react-native/assets@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@react-native/assets/-/assets-1.0.0.tgz#c6f9bf63d274bafc8e970628de24986b30a55c8e"
Expand Down