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 { Text } from 'react-native';
|
||||
|
||||
import { describe, it, expect, jest } from '@jest/globals';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react-native';
|
||||
@ -53,4 +54,36 @@ describe('Dropdown', () => {
|
||||
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 { TouchableOpacity, TouchableWithoutFeedback, Dimensions, Text, Modal, View, LayoutRectangle, ViewStyle, TextStyle, FlatList } from 'react-native';
|
||||
import { Component } from 'react';
|
||||
import * as React from 'react';
|
||||
import { TouchableOpacity, TouchableWithoutFeedback, Dimensions, Text, Modal, View, LayoutRectangle, ViewStyle, TextStyle, FlatList, LayoutChangeEvent } from 'react-native';
|
||||
import { Component, ReactElement } from 'react';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
|
||||
type ValueType = string;
|
||||
@ -29,6 +29,11 @@ interface DropdownProps {
|
||||
|
||||
selectedValue: ValueType|null;
|
||||
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 {
|
||||
@ -37,7 +42,7 @@ interface DropdownState {
|
||||
}
|
||||
|
||||
class Dropdown extends Component<DropdownProps, DropdownState> {
|
||||
private headerRef: TouchableOpacity;
|
||||
private headerRef: View;
|
||||
|
||||
public constructor(props: DropdownProps) {
|
||||
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
|
||||
this.headerRef.measure((_fx, _fy, width, height, px, py) => {
|
||||
this.setState({
|
||||
headerSize: { x: px, y: py, width: width, height: height },
|
||||
});
|
||||
const lastLayout = this.state.headerSize;
|
||||
if (px !== lastLayout.x || py !== lastLayout.y || width !== lastLayout.width || height !== lastLayout.height) {
|
||||
this.setState({
|
||||
headerSize: { x: px, y: py, width: width, height: height },
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onOpenList = () => {
|
||||
this.setState({ listVisible: true });
|
||||
};
|
||||
private onCloseList = () => {
|
||||
this.setState({ listVisible: false });
|
||||
};
|
||||
|
||||
public render() {
|
||||
const items = this.props.items;
|
||||
@ -100,10 +126,13 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
||||
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,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center' };
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
const headerStyle = { ...(this.props.headerStyle ? this.props.headerStyle : {}), flex: 1 };
|
||||
|
||||
@ -125,10 +154,6 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
||||
headerLabel = headerLabel.trim();
|
||||
}
|
||||
|
||||
const closeList = () => {
|
||||
this.setState({ listVisible: false });
|
||||
};
|
||||
|
||||
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 indentWidth = Math.min((item.depth ?? 0) * 32, dropdownWidth * 2 / 3);
|
||||
@ -139,7 +164,7 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
||||
accessibilityRole="menuitem"
|
||||
key={key}
|
||||
onPress={() => {
|
||||
closeList();
|
||||
this.onCloseList();
|
||||
if (this.props.onValueChange) this.props.onValueChange(item.value);
|
||||
}}
|
||||
>
|
||||
@ -157,7 +182,7 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
||||
const screenReaderCloseMenuButton = (
|
||||
<TouchableWithoutFeedback
|
||||
accessibilityRole='button'
|
||||
onPress={()=> closeList()}
|
||||
onPress={this.onCloseList}
|
||||
>
|
||||
<Text style={{
|
||||
opacity: 0,
|
||||
@ -168,34 +193,34 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'column' }}>
|
||||
<TouchableOpacity
|
||||
style={headerWrapperStyle as any}
|
||||
<View
|
||||
style={{ flexDirection: 'row', flex: 1, alignItems: 'center' }}
|
||||
onLayout={this.updateHeaderCoordinates}
|
||||
ref={ref => (this.headerRef = ref)}
|
||||
disabled={this.props.disabled}
|
||||
onPress={() => {
|
||||
this.updateHeaderCoordinates();
|
||||
this.setState({ listVisible: true });
|
||||
}}
|
||||
>
|
||||
<Text ellipsizeMode="tail" numberOfLines={1} style={headerStyle}>
|
||||
{headerLabel}
|
||||
</Text>
|
||||
<Text style={headerArrowStyle}>{'▼'}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={headerWrapperStyle}
|
||||
disabled={this.props.disabled}
|
||||
onPress={this.onOpenList}
|
||||
>
|
||||
<Text ellipsizeMode="tail" numberOfLines={1} style={headerStyle}>
|
||||
{headerLabel}
|
||||
</Text>
|
||||
<Text style={headerArrowStyle}>{'▼'}</Text>
|
||||
</TouchableOpacity>
|
||||
{this.state.listVisible ? null : this.props.coverableChildrenRight}
|
||||
</View>
|
||||
<Modal
|
||||
transparent={true}
|
||||
animationType='fade'
|
||||
visible={this.state.listVisible}
|
||||
onRequestClose={() => {
|
||||
closeList();
|
||||
}}
|
||||
onRequestClose={this.onCloseList}
|
||||
supportedOrientations={['landscape', 'portrait']}
|
||||
>
|
||||
<TouchableWithoutFeedback
|
||||
accessibilityElementsHidden={true}
|
||||
importantForAccessibility='no-hide-descendants'
|
||||
onPress={() => {
|
||||
closeList();
|
||||
}}
|
||||
onPress={this.onCloseList}
|
||||
style={backgroundCloseButtonStyle}
|
||||
>
|
||||
<View style={{ flex: 1 }}/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
const React = require('react');
|
||||
|
||||
import { FunctionComponent } from 'react';
|
||||
import { FunctionComponent, ReactElement } from 'react';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Folder, { FolderEntityWithChildren } from '@joplin/lib/models/Folder';
|
||||
import { themeStyle } from './global-style';
|
||||
@ -16,6 +16,7 @@ interface FolderPickerProps {
|
||||
placeholder?: string;
|
||||
darkText?: boolean;
|
||||
themeId?: number;
|
||||
coverableChildrenRight?: ReactElement|ReactElement[];
|
||||
}
|
||||
|
||||
|
||||
@ -27,6 +28,7 @@ const FolderPicker: FunctionComponent<FolderPickerProps> = ({
|
||||
folders,
|
||||
placeholder,
|
||||
darkText,
|
||||
coverableChildrenRight,
|
||||
themeId,
|
||||
}) => {
|
||||
const theme = themeStyle(themeId);
|
||||
@ -66,6 +68,7 @@ const FolderPicker: FunctionComponent<FolderPickerProps> = ({
|
||||
disabled={disabled}
|
||||
labelTransform="trim"
|
||||
selectedValue={selectedFolderId || ''}
|
||||
coverableChildrenRight={coverableChildrenRight}
|
||||
itemListStyle={{
|
||||
backgroundColor: theme.backgroundColor,
|
||||
}}
|
||||
|
@ -1,7 +1,7 @@
|
||||
const React = require('react');
|
||||
|
||||
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';
|
||||
const Icon = require('react-native-vector-icons/Ionicons').default;
|
||||
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;
|
||||
|
||||
if (folderPickerOptions && folderPickerOptions.enabled) {
|
||||
@ -613,11 +613,17 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
}}
|
||||
mustSelect={!!folderPickerOptions.mustSelect}
|
||||
folders={Folder.getRealFolders(this.props.folders)}
|
||||
coverableChildrenRight={hideableAfterTitleComponents}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
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;
|
||||
const headerItemDisabled = !(this.props.selectedNoteIds.length > 0);
|
||||
|
||||
const titleComp = createTitleComponent(headerItemDisabled);
|
||||
const sideMenuComp = !showSideMenuButton ? null : sideMenuButton(this.styles(), () => this.sideMenuButton_press());
|
||||
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 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 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 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 contextMenuStyle: ViewStyle = {
|
||||
@ -692,7 +703,6 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
this.props.showSaveButton === true,
|
||||
)}
|
||||
{titleComp}
|
||||
{pluginPanelsComp}
|
||||
{selectAllButtonComp}
|
||||
{searchButtonComp}
|
||||
{deleteButtonComp}
|
||||
|
@ -96,4 +96,5 @@ unresponded
|
||||
activeline
|
||||
Prec
|
||||
ellipsized
|
||||
Trashable
|
||||
Trashable
|
||||
hideable
|
Loading…
Reference in New Issue
Block a user