2024-09-21 13:57:26 +02:00
|
|
|
import * as React from 'react';
|
2024-11-22 12:47:22 +02:00
|
|
|
import { memo, useCallback, useMemo } from 'react';
|
2024-09-21 13:57:26 +02:00
|
|
|
import { connect } from 'react-redux';
|
2024-11-22 12:47:22 +02:00
|
|
|
import { Text, View, StyleSheet, TextStyle, ViewStyle, AccessibilityInfo, TouchableOpacity } from 'react-native';
|
2024-09-21 13:57:26 +02:00
|
|
|
import Checkbox from './Checkbox';
|
|
|
|
import Note from '@joplin/lib/models/Note';
|
|
|
|
import time from '@joplin/lib/time';
|
|
|
|
import { themeStyle } from './global-style';
|
|
|
|
import { _ } from '@joplin/lib/locale';
|
|
|
|
import { AppState } from '../utils/types';
|
|
|
|
import { Dispatch } from 'redux';
|
|
|
|
import { NoteEntity } from '@joplin/lib/services/database/types';
|
2024-11-22 12:47:22 +02:00
|
|
|
import useOnLongPressProps from '../utils/hooks/useOnLongPressProps';
|
2024-09-21 13:57:26 +02:00
|
|
|
|
|
|
|
interface Props {
|
|
|
|
dispatch: Dispatch;
|
|
|
|
themeId: number;
|
|
|
|
note: NoteEntity;
|
|
|
|
noteSelectionEnabled: boolean;
|
|
|
|
selectedNoteIds: string[];
|
|
|
|
}
|
2017-08-01 19:59:01 +02:00
|
|
|
|
2024-09-21 13:57:26 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const useStyles = (themeId: number) => {
|
|
|
|
return useMemo(() => {
|
|
|
|
const theme = themeStyle(themeId);
|
|
|
|
|
|
|
|
const listItem: ViewStyle = {
|
|
|
|
flexDirection: 'row',
|
|
|
|
// height: 40,
|
|
|
|
borderBottomWidth: 1,
|
|
|
|
borderBottomColor: theme.dividerColor,
|
|
|
|
alignItems: 'flex-start',
|
|
|
|
paddingLeft: theme.marginLeft,
|
|
|
|
paddingRight: theme.marginRight,
|
|
|
|
paddingTop: theme.itemMarginTop,
|
|
|
|
paddingBottom: theme.itemMarginBottom,
|
|
|
|
// backgroundColor: theme.backgroundColor,
|
|
|
|
};
|
2024-09-21 13:57:26 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const listItemWithCheckbox = { ...listItem };
|
|
|
|
delete listItemWithCheckbox.paddingTop;
|
|
|
|
delete listItemWithCheckbox.paddingBottom;
|
|
|
|
delete listItemWithCheckbox.paddingLeft;
|
2017-07-23 00:52:24 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const listItemText: TextStyle = {
|
|
|
|
flex: 1,
|
|
|
|
color: theme.color,
|
|
|
|
fontSize: theme.fontSize,
|
|
|
|
};
|
2017-08-01 19:59:01 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const listItemTextWithCheckbox = { ...listItemText };
|
|
|
|
listItemTextWithCheckbox.marginTop = theme.itemMarginTop - 1;
|
|
|
|
listItemTextWithCheckbox.marginBottom = listItem.paddingBottom;
|
|
|
|
|
|
|
|
const selectionWrapper: ViewStyle = { };
|
|
|
|
|
|
|
|
const selectionWrapperSelected = { ...selectionWrapper };
|
|
|
|
selectionWrapperSelected.backgroundColor = theme.selectedColor;
|
|
|
|
|
|
|
|
return StyleSheet.create({
|
|
|
|
listItem,
|
|
|
|
listItemText,
|
|
|
|
selectionWrapper,
|
|
|
|
listItemWithCheckbox,
|
|
|
|
listItemTextWithCheckbox,
|
|
|
|
selectionWrapperSelected,
|
2024-09-21 13:57:26 +02:00
|
|
|
checkboxStyle: {
|
|
|
|
color: theme.color,
|
|
|
|
paddingRight: 10,
|
|
|
|
paddingTop: theme.itemMarginTop,
|
|
|
|
paddingBottom: theme.itemMarginBottom,
|
|
|
|
paddingLeft: theme.marginLeft,
|
|
|
|
},
|
|
|
|
checkedOpacityStyle: {
|
|
|
|
opacity: 0.4,
|
|
|
|
},
|
|
|
|
uncheckedOpacityStyle: { },
|
2024-11-22 12:47:22 +02:00
|
|
|
});
|
|
|
|
}, [themeId]);
|
|
|
|
};
|
2017-11-23 20:47:51 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const NoteItemComponent: React.FC<Props> = memo(props => {
|
|
|
|
const styles = useStyles(props.themeId);
|
2017-08-01 19:59:01 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const todoCheckbox_change = useCallback(async (checked: boolean) => {
|
|
|
|
if (!props.note) return;
|
2017-07-25 20:36:52 +02:00
|
|
|
|
|
|
|
const newNote = {
|
2024-11-22 12:47:22 +02:00
|
|
|
id: props.note.id,
|
2017-07-25 20:36:52 +02:00
|
|
|
todo_completed: checked ? time.unixMs() : 0,
|
2019-07-29 15:43:53 +02:00
|
|
|
};
|
2017-07-25 20:36:52 +02:00
|
|
|
await Note.save(newNote);
|
2024-02-20 01:09:34 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
props.dispatch({ type: 'NOTE_SORT' });
|
|
|
|
}, [props.note, props.dispatch]);
|
2017-07-25 20:36:52 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const onPress = useCallback(() => {
|
|
|
|
if (!props.note) return;
|
|
|
|
if (props.note.encryption_applied) return;
|
2017-07-25 20:36:52 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
if (props.noteSelectionEnabled) {
|
|
|
|
props.dispatch({
|
2018-03-09 22:59:12 +02:00
|
|
|
type: 'NOTE_SELECTION_TOGGLE',
|
2024-11-22 12:47:22 +02:00
|
|
|
id: props.note.id,
|
2017-11-23 20:47:51 +02:00
|
|
|
});
|
|
|
|
} else {
|
2024-11-22 12:47:22 +02:00
|
|
|
props.dispatch({
|
2018-03-09 22:59:12 +02:00
|
|
|
type: 'NAV_GO',
|
|
|
|
routeName: 'Note',
|
2024-11-22 12:47:22 +02:00
|
|
|
noteId: props.note.id,
|
2017-11-23 20:47:51 +02:00
|
|
|
});
|
|
|
|
}
|
2024-11-22 12:47:22 +02:00
|
|
|
}, [props.note, props.noteSelectionEnabled, props.dispatch]);
|
2017-11-23 20:47:51 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const onLongPress = useCallback(() => {
|
|
|
|
if (!props.note) return;
|
2017-11-23 20:47:51 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
if (!props.noteSelectionEnabled) {
|
|
|
|
AccessibilityInfo.announceForAccessibility(_('Entering selection mode'));
|
|
|
|
}
|
|
|
|
|
|
|
|
props.dispatch({
|
|
|
|
type: props.noteSelectionEnabled ? 'NOTE_SELECTION_TOGGLE' : 'NOTE_SELECTION_START',
|
|
|
|
id: props.note.id,
|
2017-07-25 20:36:52 +02:00
|
|
|
});
|
2024-11-22 12:47:22 +02:00
|
|
|
}, [props.dispatch, props.note, props.noteSelectionEnabled]);
|
|
|
|
|
2017-07-25 20:36:52 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const note = props.note ?? {};
|
|
|
|
const isTodo = !!Number(note.is_todo);
|
|
|
|
const checkboxChecked = !!Number(note.todo_completed);
|
2017-07-23 00:52:24 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const checkboxStyle = styles.checkboxStyle;
|
|
|
|
const listItemStyle = isTodo ? styles.listItemWithCheckbox : styles.listItem;
|
|
|
|
const listItemTextStyle = isTodo ? styles.listItemTextWithCheckbox : styles.listItemText;
|
|
|
|
const opacityStyle = isTodo && checkboxChecked ? styles.checkedOpacityStyle : styles.uncheckedOpacityStyle;
|
|
|
|
const isSelected = props.noteSelectionEnabled && props.selectedNoteIds.includes(note.id);
|
2017-11-23 20:47:51 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const selectionWrapperStyle = isSelected ? styles.selectionWrapperSelected : styles.selectionWrapper;
|
2017-07-23 20:26:50 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const noteTitle = Note.displayTitle(note);
|
2022-06-26 19:23:41 +02:00
|
|
|
|
2024-11-22 12:47:22 +02:00
|
|
|
const selectDeselectLabel = isSelected ? _('Deselect') : _('Select');
|
|
|
|
const onLongPressProps = useOnLongPressProps({ onLongPress, actionDescription: selectDeselectLabel });
|
|
|
|
|
|
|
|
const contextMenuProps = {
|
|
|
|
// Web only.
|
|
|
|
onContextMenu: onLongPressProps.onContextMenu,
|
|
|
|
};
|
|
|
|
return (
|
|
|
|
<View
|
|
|
|
// context menu listeners need to be added to a parent view of the
|
|
|
|
// TouchableOpacity -- on web, TouchableOpacity registers a custom
|
|
|
|
// onContextMenu handler that can't be overridden.
|
|
|
|
{...contextMenuProps}
|
|
|
|
>
|
2024-11-20 13:35:35 +02:00
|
|
|
<TouchableOpacity
|
|
|
|
activeOpacity={0.5}
|
2024-11-22 12:47:22 +02:00
|
|
|
onPress={onPress}
|
2024-11-20 13:35:35 +02:00
|
|
|
accessibilityRole='button'
|
2024-11-22 12:47:22 +02:00
|
|
|
accessibilityHint={props.noteSelectionEnabled ? '' : _('Opens note')}
|
|
|
|
aria-pressed={props.noteSelectionEnabled ? isSelected : undefined}
|
|
|
|
accessibilityState={{ selected: isSelected }}
|
|
|
|
{...onLongPressProps}
|
2024-11-20 13:35:35 +02:00
|
|
|
>
|
2024-11-22 12:47:22 +02:00
|
|
|
<View style={[selectionWrapperStyle, opacityStyle, listItemStyle]}>
|
|
|
|
{isTodo ? <Checkbox
|
|
|
|
style={checkboxStyle}
|
|
|
|
checked={checkboxChecked}
|
|
|
|
onChange={todoCheckbox_change}
|
|
|
|
accessibilityLabel={_('to-do: %s', noteTitle)}
|
|
|
|
/> : null }
|
|
|
|
<Text style={listItemTextStyle}>{noteTitle}</Text>
|
2017-07-23 00:52:24 +02:00
|
|
|
</View>
|
2017-11-23 20:47:51 +02:00
|
|
|
</TouchableOpacity>
|
2024-11-22 12:47:22 +02:00
|
|
|
</View>
|
|
|
|
);
|
|
|
|
});
|
2017-07-23 00:52:24 +02:00
|
|
|
|
2024-09-21 13:57:26 +02:00
|
|
|
export default connect((state: AppState) => {
|
2019-07-29 15:43:53 +02:00
|
|
|
return {
|
2020-09-15 15:01:07 +02:00
|
|
|
themeId: state.settings.theme,
|
2019-07-29 15:43:53 +02:00
|
|
|
noteSelectionEnabled: state.noteSelectionEnabled,
|
|
|
|
selectedNoteIds: state.selectedNoteIds,
|
|
|
|
};
|
|
|
|
})(NoteItemComponent);
|
2017-07-23 00:52:24 +02:00
|
|
|
|