mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Android: Plugins: Autohide the plugin panel toggle in toolbar to increase size for notebook dropdown (#10212)
This commit is contained in:
parent
9b5ee63638
commit
d3e2d3fc4a
@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { Text } from 'react-native';
|
||||||
|
|
||||||
import { describe, it, expect, jest } from '@jest/globals';
|
import { describe, it, expect, jest } from '@jest/globals';
|
||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react-native';
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react-native';
|
||||||
@ -53,4 +54,36 @@ describe('Dropdown', () => {
|
|||||||
expect(screen.queryByText('Item 2')).not.toBeNull();
|
expect(screen.queryByText('Item 2')).not.toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should hide coverableChildren to increase space', async () => {
|
||||||
|
render(
|
||||||
|
<Dropdown
|
||||||
|
items={[{ label: 'Test1', value: '1' }, { label: 'Test2', value: '2' }, { label: 'Test3', value: '3' }]}
|
||||||
|
selectedValue={'1'}
|
||||||
|
onValueChange={()=>{}}
|
||||||
|
coverableChildrenRight={<Text>Elem Right</Text>}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
expect(screen.queryByText('Test2')).toBeNull();
|
||||||
|
expect(screen.getByText('Elem Right')).not.toBeNull();
|
||||||
|
|
||||||
|
// Open the dropdown
|
||||||
|
fireEvent.press(screen.getByText('Test1'));
|
||||||
|
|
||||||
|
// Should show the dropdown and hide the right content.
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('Test2')).not.toBeNull();
|
||||||
|
});
|
||||||
|
expect(screen.queryByText('Elem Right')).toBeNull();
|
||||||
|
|
||||||
|
|
||||||
|
// Should hide the dropdown and show the right content.
|
||||||
|
fireEvent.press(screen.getByText('Test3'));
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('Test2')).toBeNull();
|
||||||
|
});
|
||||||
|
expect(screen.queryByText('Elem Right')).not.toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const React = require('react');
|
import * as React from 'react';
|
||||||
import { TouchableOpacity, TouchableWithoutFeedback, Dimensions, Text, Modal, View, LayoutRectangle, ViewStyle, TextStyle, FlatList } from 'react-native';
|
import { TouchableOpacity, TouchableWithoutFeedback, Dimensions, Text, Modal, View, LayoutRectangle, ViewStyle, TextStyle, FlatList, LayoutChangeEvent } from 'react-native';
|
||||||
import { Component } from 'react';
|
import { Component, ReactElement } from 'react';
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
|
||||||
type ValueType = string;
|
type ValueType = string;
|
||||||
@ -29,6 +29,11 @@ interface DropdownProps {
|
|||||||
|
|
||||||
selectedValue: ValueType|null;
|
selectedValue: ValueType|null;
|
||||||
onValueChange?: OnValueChangedListener;
|
onValueChange?: OnValueChangedListener;
|
||||||
|
|
||||||
|
// Shown to the right of the dropdown when closed, hidden when opened.
|
||||||
|
// Avoids abrupt size transitions that would be caused by externally resizing the space
|
||||||
|
// available for the dropdown on open/close.
|
||||||
|
coverableChildrenRight?: ReactElement[]|ReactElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DropdownState {
|
interface DropdownState {
|
||||||
@ -37,7 +42,7 @@ interface DropdownState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Dropdown extends Component<DropdownProps, DropdownState> {
|
class Dropdown extends Component<DropdownProps, DropdownState> {
|
||||||
private headerRef: TouchableOpacity;
|
private headerRef: View;
|
||||||
|
|
||||||
public constructor(props: DropdownProps) {
|
public constructor(props: DropdownProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -49,14 +54,35 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateHeaderCoordinates() {
|
private updateHeaderCoordinates = (event: LayoutChangeEvent) => {
|
||||||
|
if (!this.headerRef) return;
|
||||||
|
|
||||||
|
const { width, height } = event.nativeEvent.layout;
|
||||||
|
|
||||||
|
const lastLayout = this.state.headerSize;
|
||||||
|
if (width !== lastLayout.width || height !== lastLayout.height) {
|
||||||
|
this.setState({
|
||||||
|
headerSize: { x: lastLayout.x, y: lastLayout.y, width, height },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/30096038/react-native-getting-the-position-of-an-element
|
// https://stackoverflow.com/questions/30096038/react-native-getting-the-position-of-an-element
|
||||||
this.headerRef.measure((_fx, _fy, width, height, px, py) => {
|
this.headerRef.measure((_fx, _fy, width, height, px, py) => {
|
||||||
|
const lastLayout = this.state.headerSize;
|
||||||
|
if (px !== lastLayout.x || py !== lastLayout.y || width !== lastLayout.width || height !== lastLayout.height) {
|
||||||
this.setState({
|
this.setState({
|
||||||
headerSize: { x: px, y: py, width: width, height: height },
|
headerSize: { x: px, y: py, width: width, height: height },
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onOpenList = () => {
|
||||||
|
this.setState({ listVisible: true });
|
||||||
|
};
|
||||||
|
private onCloseList = () => {
|
||||||
|
this.setState({ listVisible: false });
|
||||||
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const items = this.props.items;
|
const items = this.props.items;
|
||||||
@ -100,10 +126,13 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
|||||||
paddingRight: 10,
|
paddingRight: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerWrapperStyle = { ...(this.props.headerWrapperStyle ? this.props.headerWrapperStyle : {}), height: 35,
|
const headerWrapperStyle: ViewStyle = {
|
||||||
|
...(this.props.headerWrapperStyle ? this.props.headerWrapperStyle : {}),
|
||||||
|
height: 35,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center' };
|
alignItems: 'center',
|
||||||
|
};
|
||||||
|
|
||||||
const headerStyle = { ...(this.props.headerStyle ? this.props.headerStyle : {}), flex: 1 };
|
const headerStyle = { ...(this.props.headerStyle ? this.props.headerStyle : {}), flex: 1 };
|
||||||
|
|
||||||
@ -125,10 +154,6 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
|||||||
headerLabel = headerLabel.trim();
|
headerLabel = headerLabel.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeList = () => {
|
|
||||||
this.setState({ listVisible: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
const itemRenderer = ({ item }: { item: DropdownListItem }) => {
|
const itemRenderer = ({ item }: { item: DropdownListItem }) => {
|
||||||
const key = item.value ? item.value.toString() : '__null'; // The top item ("Move item to notebook...") has a null value.
|
const key = item.value ? item.value.toString() : '__null'; // The top item ("Move item to notebook...") has a null value.
|
||||||
const indentWidth = Math.min((item.depth ?? 0) * 32, dropdownWidth * 2 / 3);
|
const indentWidth = Math.min((item.depth ?? 0) * 32, dropdownWidth * 2 / 3);
|
||||||
@ -139,7 +164,7 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
|||||||
accessibilityRole="menuitem"
|
accessibilityRole="menuitem"
|
||||||
key={key}
|
key={key}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
closeList();
|
this.onCloseList();
|
||||||
if (this.props.onValueChange) this.props.onValueChange(item.value);
|
if (this.props.onValueChange) this.props.onValueChange(item.value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -157,7 +182,7 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
|||||||
const screenReaderCloseMenuButton = (
|
const screenReaderCloseMenuButton = (
|
||||||
<TouchableWithoutFeedback
|
<TouchableWithoutFeedback
|
||||||
accessibilityRole='button'
|
accessibilityRole='button'
|
||||||
onPress={()=> closeList()}
|
onPress={this.onCloseList}
|
||||||
>
|
>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
@ -168,34 +193,34 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, flexDirection: 'column' }}>
|
<View style={{ flex: 1, flexDirection: 'column' }}>
|
||||||
<TouchableOpacity
|
<View
|
||||||
style={headerWrapperStyle as any}
|
style={{ flexDirection: 'row', flex: 1, alignItems: 'center' }}
|
||||||
|
onLayout={this.updateHeaderCoordinates}
|
||||||
ref={ref => (this.headerRef = ref)}
|
ref={ref => (this.headerRef = ref)}
|
||||||
|
>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={headerWrapperStyle}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
onPress={() => {
|
onPress={this.onOpenList}
|
||||||
this.updateHeaderCoordinates();
|
|
||||||
this.setState({ listVisible: true });
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Text ellipsizeMode="tail" numberOfLines={1} style={headerStyle}>
|
<Text ellipsizeMode="tail" numberOfLines={1} style={headerStyle}>
|
||||||
{headerLabel}
|
{headerLabel}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={headerArrowStyle}>{'▼'}</Text>
|
<Text style={headerArrowStyle}>{'▼'}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
{this.state.listVisible ? null : this.props.coverableChildrenRight}
|
||||||
|
</View>
|
||||||
<Modal
|
<Modal
|
||||||
transparent={true}
|
transparent={true}
|
||||||
|
animationType='fade'
|
||||||
visible={this.state.listVisible}
|
visible={this.state.listVisible}
|
||||||
onRequestClose={() => {
|
onRequestClose={this.onCloseList}
|
||||||
closeList();
|
|
||||||
}}
|
|
||||||
supportedOrientations={['landscape', 'portrait']}
|
supportedOrientations={['landscape', 'portrait']}
|
||||||
>
|
>
|
||||||
<TouchableWithoutFeedback
|
<TouchableWithoutFeedback
|
||||||
accessibilityElementsHidden={true}
|
accessibilityElementsHidden={true}
|
||||||
importantForAccessibility='no-hide-descendants'
|
importantForAccessibility='no-hide-descendants'
|
||||||
onPress={() => {
|
onPress={this.onCloseList}
|
||||||
closeList();
|
|
||||||
}}
|
|
||||||
style={backgroundCloseButtonStyle}
|
style={backgroundCloseButtonStyle}
|
||||||
>
|
>
|
||||||
<View style={{ flex: 1 }}/>
|
<View style={{ flex: 1 }}/>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
import { FunctionComponent } from 'react';
|
import { FunctionComponent, ReactElement } from 'react';
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
import Folder, { FolderEntityWithChildren } from '@joplin/lib/models/Folder';
|
import Folder, { FolderEntityWithChildren } from '@joplin/lib/models/Folder';
|
||||||
import { themeStyle } from './global-style';
|
import { themeStyle } from './global-style';
|
||||||
@ -16,6 +16,7 @@ interface FolderPickerProps {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
darkText?: boolean;
|
darkText?: boolean;
|
||||||
themeId?: number;
|
themeId?: number;
|
||||||
|
coverableChildrenRight?: ReactElement|ReactElement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ const FolderPicker: FunctionComponent<FolderPickerProps> = ({
|
|||||||
folders,
|
folders,
|
||||||
placeholder,
|
placeholder,
|
||||||
darkText,
|
darkText,
|
||||||
|
coverableChildrenRight,
|
||||||
themeId,
|
themeId,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = themeStyle(themeId);
|
const theme = themeStyle(themeId);
|
||||||
@ -66,6 +68,7 @@ const FolderPicker: FunctionComponent<FolderPickerProps> = ({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
labelTransform="trim"
|
labelTransform="trim"
|
||||||
selectedValue={selectedFolderId || ''}
|
selectedValue={selectedFolderId || ''}
|
||||||
|
coverableChildrenRight={coverableChildrenRight}
|
||||||
itemListStyle={{
|
itemListStyle={{
|
||||||
backgroundColor: theme.backgroundColor,
|
backgroundColor: theme.backgroundColor,
|
||||||
}}
|
}}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { PureComponent } from 'react';
|
import { PureComponent, ReactElement } from 'react';
|
||||||
import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView, Dimensions, ViewStyle } from 'react-native';
|
import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView, Dimensions, ViewStyle } from 'react-native';
|
||||||
const Icon = require('react-native-vector-icons/Ionicons').default;
|
const Icon = require('react-native-vector-icons/Ionicons').default;
|
||||||
const { BackButtonService } = require('../services/back-button.js');
|
const { BackButtonService } = require('../services/back-button.js');
|
||||||
@ -573,7 +573,7 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const createTitleComponent = (disabled: boolean) => {
|
const createTitleComponent = (disabled: boolean, hideableAfterTitleComponents: ReactElement) => {
|
||||||
const folderPickerOptions = this.props.folderPickerOptions;
|
const folderPickerOptions = this.props.folderPickerOptions;
|
||||||
|
|
||||||
if (folderPickerOptions && folderPickerOptions.enabled) {
|
if (folderPickerOptions && folderPickerOptions.enabled) {
|
||||||
@ -613,11 +613,17 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
|||||||
}}
|
}}
|
||||||
mustSelect={!!folderPickerOptions.mustSelect}
|
mustSelect={!!folderPickerOptions.mustSelect}
|
||||||
folders={Folder.getRealFolders(this.props.folders)}
|
folders={Folder.getRealFolders(this.props.folders)}
|
||||||
|
coverableChildrenRight={hideableAfterTitleComponents}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const title = 'title' in this.props && this.props.title !== null ? this.props.title : '';
|
const title = 'title' in this.props && this.props.title !== null ? this.props.title : '';
|
||||||
return <Text ellipsizeMode={'tail'} numberOfLines={1} style={this.styles().titleText}>{title}</Text>;
|
return (
|
||||||
|
<>
|
||||||
|
<Text ellipsizeMode={'tail'} numberOfLines={1} style={this.styles().titleText}>{title}</Text>
|
||||||
|
{hideableAfterTitleComponents}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -642,16 +648,21 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
|||||||
if (this.props.noteSelectionEnabled) backButtonDisabled = false;
|
if (this.props.noteSelectionEnabled) backButtonDisabled = false;
|
||||||
const headerItemDisabled = !(this.props.selectedNoteIds.length > 0);
|
const headerItemDisabled = !(this.props.selectedNoteIds.length > 0);
|
||||||
|
|
||||||
const titleComp = createTitleComponent(headerItemDisabled);
|
|
||||||
const sideMenuComp = !showSideMenuButton ? null : sideMenuButton(this.styles(), () => this.sideMenuButton_press());
|
const sideMenuComp = !showSideMenuButton ? null : sideMenuButton(this.styles(), () => this.sideMenuButton_press());
|
||||||
const backButtonComp = !showBackButton ? null : backButton(this.styles(), () => this.backButton_press(), backButtonDisabled);
|
const backButtonComp = !showBackButton ? null : backButton(this.styles(), () => this.backButton_press(), backButtonDisabled);
|
||||||
|
const pluginPanelsComp = pluginPanelToggleButton(this.styles(), () => this.pluginPanelToggleButton_press());
|
||||||
const selectAllButtonComp = !showSelectAllButton ? null : selectAllButton(this.styles(), () => this.selectAllButton_press());
|
const selectAllButtonComp = !showSelectAllButton ? null : selectAllButton(this.styles(), () => this.selectAllButton_press());
|
||||||
const searchButtonComp = !showSearchButton ? null : searchButton(this.styles(), () => this.searchButton_press());
|
const searchButtonComp = !showSearchButton ? null : searchButton(this.styles(), () => this.searchButton_press());
|
||||||
const pluginPanelsComp = pluginPanelToggleButton(this.styles(), () => this.pluginPanelToggleButton_press());
|
|
||||||
const deleteButtonComp = !selectedFolderInTrash && this.props.noteSelectionEnabled ? deleteButton(this.styles(), () => this.deleteButton_press(), headerItemDisabled) : null;
|
const deleteButtonComp = !selectedFolderInTrash && this.props.noteSelectionEnabled ? deleteButton(this.styles(), () => this.deleteButton_press(), headerItemDisabled) : null;
|
||||||
const restoreButtonComp = selectedFolderInTrash && this.props.noteSelectionEnabled ? restoreButton(this.styles(), () => this.restoreButton_press(), headerItemDisabled) : null;
|
const restoreButtonComp = selectedFolderInTrash && this.props.noteSelectionEnabled ? restoreButton(this.styles(), () => this.restoreButton_press(), headerItemDisabled) : null;
|
||||||
const duplicateButtonComp = !selectedFolderInTrash && this.props.noteSelectionEnabled ? duplicateButton(this.styles(), () => this.duplicateButton_press(), headerItemDisabled) : null;
|
const duplicateButtonComp = !selectedFolderInTrash && this.props.noteSelectionEnabled ? duplicateButton(this.styles(), () => this.duplicateButton_press(), headerItemDisabled) : null;
|
||||||
const sortButtonComp = !this.props.noteSelectionEnabled && this.props.sortButton_press ? sortButton(this.styles(), () => this.props.sortButton_press()) : null;
|
const sortButtonComp = !this.props.noteSelectionEnabled && this.props.sortButton_press ? sortButton(this.styles(), () => this.props.sortButton_press()) : null;
|
||||||
|
|
||||||
|
// To allow the notebook dropdown (and perhaps other components) to have sufficient
|
||||||
|
// space while in use, we allow certain buttons to be hidden.
|
||||||
|
const hideableRightComponents = pluginPanelsComp;
|
||||||
|
|
||||||
|
const titleComp = createTitleComponent(headerItemDisabled, hideableRightComponents);
|
||||||
const windowHeight = Dimensions.get('window').height - 50;
|
const windowHeight = Dimensions.get('window').height - 50;
|
||||||
|
|
||||||
const contextMenuStyle: ViewStyle = {
|
const contextMenuStyle: ViewStyle = {
|
||||||
@ -692,7 +703,6 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
|||||||
this.props.showSaveButton === true,
|
this.props.showSaveButton === true,
|
||||||
)}
|
)}
|
||||||
{titleComp}
|
{titleComp}
|
||||||
{pluginPanelsComp}
|
|
||||||
{selectAllButtonComp}
|
{selectAllButtonComp}
|
||||||
{searchButtonComp}
|
{searchButtonComp}
|
||||||
{deleteButtonComp}
|
{deleteButtonComp}
|
||||||
|
@ -97,3 +97,4 @@ activeline
|
|||||||
Prec
|
Prec
|
||||||
ellipsized
|
ellipsized
|
||||||
Trashable
|
Trashable
|
||||||
|
hideable
|
Loading…
Reference in New Issue
Block a user