Skip to content
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,7 @@ packages/app-mobile/components/screens/JoplinCloudLoginScreen.js
packages/app-mobile/components/screens/LogScreen.js
packages/app-mobile/components/screens/Note/Note.test.js
packages/app-mobile/components/screens/Note/Note.js
packages/app-mobile/components/screens/Note/NoteLockPanel.js
packages/app-mobile/components/screens/Note/commands/attachFile.js
packages/app-mobile/components/screens/Note/commands/hideKeyboard.js
packages/app-mobile/components/screens/Note/commands/index.js
Expand Down Expand Up @@ -1402,6 +1403,7 @@ packages/lib/components/shared/config/plugins/useOnInstallHandler.js
packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.js
packages/lib/components/shared/config/shouldShowMissingPasswordWarning.js
packages/lib/components/shared/dropbox-login-shared.js
packages/lib/components/shared/note-screen-shared.test.js
packages/lib/components/shared/note-screen-shared.js
packages/lib/components/shared/reduxSharedMiddleware.js
packages/lib/components/shared/side-menu-shared.test.js
Expand Down
2 changes: 2 additions & 0 deletions .ignore.eslint
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,7 @@ packages/app-mobile/components/screens/JoplinCloudLoginScreen.js
packages/app-mobile/components/screens/LogScreen.js
packages/app-mobile/components/screens/Note/Note.test.js
packages/app-mobile/components/screens/Note/Note.js
packages/app-mobile/components/screens/Note/NoteLockPanel.js
packages/app-mobile/components/screens/Note/commands/attachFile.js
packages/app-mobile/components/screens/Note/commands/hideKeyboard.js
packages/app-mobile/components/screens/Note/commands/index.js
Expand Down Expand Up @@ -1428,6 +1429,7 @@ packages/lib/components/shared/config/plugins/useOnInstallHandler.js
packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.js
packages/lib/components/shared/config/shouldShowMissingPasswordWarning.js
packages/lib/components/shared/dropbox-login-shared.js
packages/lib/components/shared/note-screen-shared.test.js
packages/lib/components/shared/note-screen-shared.js
packages/lib/components/shared/reduxSharedMiddleware.js
packages/lib/components/shared/side-menu-shared.test.js
Expand Down
32 changes: 28 additions & 4 deletions packages/app-mobile/components/NoteItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { memo, useCallback, useMemo } from 'react';
import { memo, useCallback, useContext, useMemo, useState } from 'react';
import { connect } from 'react-redux';
import { Text, StyleSheet, TextStyle, View, ViewStyle, AccessibilityInfo } from 'react-native';
import Checkbox from './Checkbox';
Expand All @@ -16,6 +16,8 @@ import { escapeRegExp } from '@joplin/lib/string-utils';
import isNoteLockEnabled from '@joplin/lib/services/noteLock/isNoteLockEnabled';
import NoteLockNote from '@joplin/lib/services/noteLock/NoteLockNote';
import NoteLockSession from '@joplin/lib/services/noteLock/NoteLockSession';
import { DialogContext } from './DialogManager';
import Icon from './Icon';

interface Props {
dispatch: Dispatch;
Expand Down Expand Up @@ -78,6 +80,15 @@ const useStyles = (themeId: number, showTopBorder: boolean) => {
return StyleSheet.create({
listItemDivider,
listItemText,
titleRow: {
flexDirection: 'row',
alignItems: 'center',
},
lockIcon: {
color: theme.colorFaded,
fontSize: theme.fontSize,
marginRight: 8,
},
selectionWrapper,
listItemPressableWithoutCheckbox,
listItemPressableWithCheckbox,
Expand All @@ -102,6 +113,8 @@ const useStyles = (themeId: number, showTopBorder: boolean) => {

const NoteItemComponent: React.FC<Props> = memo(props => {
const styles = useStyles(props.themeId, props.index !== 0);
const dialogs = useContext(DialogContext);
const [checkboxKey, setCheckboxKey] = useState(0);

const todoCheckbox_change = useCallback(async (checked: boolean) => {
if (!props.note) return;
Expand All @@ -110,7 +123,10 @@ const NoteItemComponent: React.FC<Props> = memo(props => {
if (isNoteLockEnabled()) {
const lockState = await Note.load(props.note.id, { fields: ['is_locked'] });
if (NoteLockNote.isLocked(lockState) && !NoteLockSession.instance().isUnlocked()) {
throw new Error('Cannot change a locked note while the session is locked');
// The checkbox keeps its own checked state, so a remount reverts the tick.
setCheckboxKey(key => key + 1);
await dialogs.error(_('Cannot change a locked note while the session is locked'));
return;
}
}

Expand All @@ -121,7 +137,7 @@ const NoteItemComponent: React.FC<Props> = memo(props => {
await Note.save(newNote);

props.dispatch({ type: 'NOTE_SORT' });
}, [props.note, props.dispatch]);
}, [props.note, props.dispatch, dialogs]);

const onPress = useCallback(() => {
if (!props.note) return;
Expand Down Expand Up @@ -175,12 +191,15 @@ const NoteItemComponent: React.FC<Props> = memo(props => {
const onLongPressProps = useOnLongPressProps({ onLongPress, actionDescription: selectDeselectLabel });

const todoCheckbox = isTodo ? <Checkbox
key={checkboxKey}
style={checkboxStyle}
checked={checkboxChecked}
onChange={todoCheckbox_change}
accessibilityLabel={_('to-do: %s', noteTitle)}
/> : null;

const titleElement = <Text style={listItemTextStyle}>{displayedNoteTitle}</Text>;

const pressableProps = {
style: isTodo ? styles.listItemPressableWithCheckbox : styles.listItemPressableWithoutCheckbox,
accessibilityHint: props.noteSelectionEnabled ? '' : _('Opens note'),
Expand All @@ -199,7 +218,12 @@ const NoteItemComponent: React.FC<Props> = memo(props => {
onPress={onPress}
beforePressable={todoCheckbox}
>
<Text style={listItemTextStyle}>{displayedNoteTitle}</Text>
{isNoteLockEnabled() ? (
<View style={styles.titleRow}>
{!!note.is_locked && <Icon name='fas fa-lock' style={styles.lockIcon} accessibilityLabel={_('Locked')} />}
{titleElement}
</View>
) : titleElement}
</MultiTouchableOpacity>
</View>
);
Expand Down
Loading
Loading