Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: Mobile: Use stronger types in side-menu and support FontAwesome icons #11247

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
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
175 changes: 104 additions & 71 deletions packages/app-mobile/components/side-menu-content.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
const React = require('react');
import * as React from 'react';
import { useMemo, useEffect, useCallback, useContext } from 'react';
const { Easing, Animated, TouchableOpacity, Text, StyleSheet, ScrollView, View, Image } = require('react-native');
const { connect } = require('react-redux');
const Icon = require('react-native-vector-icons/Ionicons').default;
import { Easing, Animated, TouchableOpacity, Text, StyleSheet, ScrollView, View, Image, ImageStyle } from 'react-native';
import { Dispatch } from 'redux';
import { connect } from 'react-redux';
const IonIcon = require('react-native-vector-icons/Ionicons').default;
import Icon from './Icon';
import Folder from '@joplin/lib/models/Folder';
import Synchronizer from '@joplin/lib/Synchronizer';
import NavService from '@joplin/lib/services/NavService';
import { _ } from '@joplin/lib/locale';
import { ThemeStyle, themeStyle } from './global-style';
import { themeStyle } from './global-style';
import { buildFolderTree, isFolderSelected, renderFolders } from '@joplin/lib/components/shared/side-menu-shared';
import { FolderEntity, FolderIcon, FolderIconType } from '@joplin/lib/services/database/types';
import { AppState } from '../utils/types';
Expand All @@ -19,26 +21,24 @@ import restoreItems from '@joplin/lib/services/trash/restoreItems';
import emptyTrash from '@joplin/lib/services/trash/emptyTrash';
import { ModelType } from '@joplin/lib/BaseModel';
import { DialogContext } from './DialogManager';
import { TextStyle, ViewStyle } from 'react-native';
import { StateDecryptionWorker, StateResourceFetcher } from '@joplin/lib/reducer';
const { TouchableRipple } = require('react-native-paper');
const { substrWithEllipsis } = require('@joplin/lib/string-utils');

interface Props {
syncStarted: boolean;
themeId: number;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
dispatch: Function;
dispatch: Dispatch;
collapsedFolderIds: string[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
syncReport: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
decryptionWorker: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
resourceFetcher: any;
decryptionWorker: StateDecryptionWorker;
resourceFetcher: StateResourceFetcher;
syncOnlyOverWifi: boolean;
isOnMobileData: boolean;
notesParentType: string;
folders: FolderEntity[];
opacity: number;
profileConfig: ProfileConfig;
inboxJopId: string;
selectedFolderId: string;
Expand All @@ -54,68 +54,103 @@ const syncIconRotation = syncIconRotationValue.interpolate({

const folderIconRightMargin = 10;

// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
let syncIconAnimation: any;
let syncIconAnimation: Animated.CompositeAnimation|null = null;

const SideMenuContentComponent = (props: Props) => {
const alwaysShowFolderIcons = useMemo(() => Folder.shouldShowFolderIcons(props.folders), [props.folders]);

const styles_ = useMemo(() => {
const theme = themeStyle(props.themeId);

// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const styles: any = {
const buttonStyle: ViewStyle = {
flex: 1,
flexDirection: 'row',
flexBasis: 'auto',
height: 36,
alignItems: 'center',
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
};
const buttonTextStyle: TextStyle = {
flex: 1,
color: theme.color,
paddingLeft: 10,
fontSize: theme.fontSize,
};
const sidebarIconStyle: TextStyle = {
fontSize: 22,
color: theme.color,
width: 26,
textAlign: 'center',
textAlignVertical: 'center',
};
const folderIconBase: ViewStyle&ImageStyle = {
marginRight: folderIconRightMargin,
width: 27,
};
const folderButtonStyle: ViewStyle = {
...buttonStyle,
paddingLeft: 0,
};
const sideButtonStyle: ViewStyle = {
...buttonStyle,
flex: 0,
};

const styles = StyleSheet.create({
menu: {
flex: 1,
backgroundColor: theme.backgroundColor,
},
button: {
flex: 1,
flexDirection: 'row',
flexBasis: 'auto',
height: 36,
alignItems: 'center',
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
},
buttonText: {
flex: 1,
color: theme.color,
paddingLeft: 10,
fontSize: theme.fontSize,
},
button: buttonStyle,
buttonText: buttonTextStyle,
syncStatus: {
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
color: theme.colorFaded,
fontSize: theme.fontSizeSmaller,
flex: 0,
},
sidebarIcon: {
fontSize: 22,
color: theme.color,
width: 26,
textAlign: 'center',
textAlignVertical: 'center',
sidebarIcon: sidebarIconStyle,
folderButton: folderButtonStyle,
folderButtonText: {
...buttonTextStyle,
paddingLeft: 0,
},
};

styles.folderButton = { ...styles.button };
styles.folderButton.paddingLeft = 0;
styles.folderButtonText = { ...styles.buttonText, paddingLeft: 0 };
styles.folderButtonSelected = { ...styles.folderButton };
styles.folderButtonSelected.backgroundColor = theme.selectedColor;
styles.folderIcon = { ...theme.icon };
styles.folderIcon.color = theme.colorFaded; // '#0072d5';
styles.folderIcon.paddingTop = 3;

styles.sideButton = { ...styles.button, flex: 0 };
styles.sideButtonSelected = { ...styles.sideButton, backgroundColor: theme.selectedColor };
styles.sideButtonText = { ...styles.buttonText };

styles.emptyFolderIcon = { ...styles.sidebarIcon, marginRight: folderIconRightMargin, width: 26 };
folderButtonSelected: {
...folderButtonStyle,
backgroundColor: theme.selectedColor,
},
folderToggleIcon: {
...theme.icon,
color: theme.colorFaded,
paddingTop: 3,
},
sideButton: sideButtonStyle,
sideButtonSelected: {
...sideButtonStyle,
},
sideButtonText: {
...buttonTextStyle,
},
folderBaseIcon: {
...sidebarIconStyle,
...folderIconBase,
},
folderEmojiIcon: {
...sidebarIconStyle,
...folderIconBase,
textAlign: undefined,
fontSize: theme.fontSize,
},
folderImageIcon: {
...folderIconBase,
height: 20,
resizeMode: 'contain',
},
});

return StyleSheet.create(styles);
return styles;
}, [props.themeId]);

useEffect(() => {
Expand Down Expand Up @@ -374,21 +409,23 @@ const SideMenuContentComponent = (props: Props) => {
if (actionDone === 'auth') props.dispatch({ type: 'SIDE_MENU_CLOSE' });
}, [performSync, props.dispatch]);

const renderFolderIcon = (folderId: string, theme: ThemeStyle, folderIcon: FolderIcon) => {
const renderFolderIcon = (folderId: string, folderIcon: FolderIcon) => {
if (!folderIcon) {
if (folderId === getTrashFolderId()) {
folderIcon = getTrashFolderIcon(FolderIconType.Emoji);
} else if (alwaysShowFolderIcons) {
return <Icon name="folder-outline" style={styles_.emptyFolderIcon} />;
return <IonIcon name="folder-outline" style={styles_.folderBaseIcon} />;
} else {
return null;
}
}

if (folderIcon.type === 1) { // FolderIconType.Emoji
return <Text style={{ fontSize: theme.fontSize, marginRight: folderIconRightMargin, width: 27 }}>{folderIcon.emoji}</Text>;
} else if (folderIcon.type === 2) { // FolderIconType.DataUrl
return <Image style={{ width: 27, height: 20, marginRight: folderIconRightMargin, resizeMode: 'contain' }} source={{ uri: folderIcon.dataUrl }}/>;
if (folderIcon.type === FolderIconType.Emoji) {
return <Text style={styles_.folderEmojiIcon}>{folderIcon.emoji}</Text>;
} else if (folderIcon.type === FolderIconType.DataUrl) {
return <Image style={styles_.folderImageIcon} source={{ uri: folderIcon.dataUrl }}/>;
} else if (folderIcon.type === FolderIconType.FontAwesome) {
return <Icon style={styles_.folderBaseIcon} name={folderIcon.name} accessibilityLabel={''}/>;
} else {
throw new Error(`Unsupported folder icon type: ${folderIcon.type}`);
}
Expand All @@ -397,8 +434,7 @@ const SideMenuContentComponent = (props: Props) => {
const renderFolderItem = (folder: FolderEntity, hasChildren: boolean, depth: number) => {
const theme = themeStyle(props.themeId);

// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const folderButtonStyle: any = {
const folderButtonStyle: ViewStyle = {
flex: 1,
flexDirection: 'row',
flexBasis: 'auto',
Expand All @@ -411,20 +447,18 @@ const SideMenuContentComponent = (props: Props) => {
if (selected) folderButtonStyle.backgroundColor = theme.selectedColor;
folderButtonStyle.paddingLeft = depth * 10 + theme.marginLeft;

// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const iconWrapperStyle: any = { paddingLeft: 10, paddingRight: 10 };
const iconWrapperStyle: ViewStyle = { paddingLeft: 10, paddingRight: 10 };
if (selected) iconWrapperStyle.backgroundColor = theme.selectedColor;

let iconWrapper = null;

const collapsed = props.collapsedFolderIds.indexOf(folder.id) >= 0;
const iconName = collapsed ? 'chevron-down' : 'chevron-up';
const iconComp = <Icon name={iconName} style={styles_.folderIcon} />;
const iconComp = <IonIcon name={iconName} style={styles_.folderToggleIcon} />;

iconWrapper = !hasChildren ? null : (
<TouchableOpacity
style={iconWrapperStyle}
folderid={folder.id}
onPress={() => {
if (hasChildren) folder_togglePress(folder);
}}
Expand Down Expand Up @@ -455,7 +489,7 @@ const SideMenuContentComponent = (props: Props) => {
role='button'
>
<View style={folderButtonStyle}>
{renderFolderIcon(folder.id, theme, folderIcon)}
{renderFolderIcon(folder.id, folderIcon)}
<Text numberOfLines={1} style={styles_.folderButtonText}>
{Folder.displayTitle(folder)}
</Text>
Expand All @@ -466,9 +500,8 @@ const SideMenuContentComponent = (props: Props) => {
);
};

// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
const renderSidebarButton = (key: string, title: string, iconName: string, onPressHandler: Function = null, selected = false) => {
let icon = <Icon name={iconName} style={styles_.sidebarIcon} aria-hidden={true} />;
const renderSidebarButton = (key: string, title: string, iconName: string, onPressHandler: ()=> void = null, selected = false) => {
let icon = <IonIcon name={iconName} style={styles_.sidebarIcon} aria-hidden={true} />;

if (key === 'synchronize_button') {
icon = <Animated.View style={{ transform: [{ rotate: syncIconRotation }] }}>{icon}</Animated.View>;
Expand Down Expand Up @@ -590,7 +623,7 @@ const SideMenuContentComponent = (props: Props) => {

return (
<View style={style}>
<View style={{ flex: 1, opacity: props.opacity }}>
<View style={{ flex: 1 }}>
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

props.opacity seems to have always been undefined (not set).

<ScrollView scrollsToTop={false} style={styles_.menu}>
{items}
</ScrollView>
Expand Down
Loading