mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 00:06:23 +00:00
fix: lay out the reader's pages at the pane's width so a tablet pane is not left empty
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
// Tablet, landscape: the first message opened after a relaunch and a
|
||||||
|
// rotation showed an empty reading pane (toolbar and bottom bar only). The
|
||||||
|
// pager laid its pages out at the window's width before it knew the pane's,
|
||||||
|
// so initialScrollIndex put the open message at the wrong offset, and
|
||||||
|
// removeClippedSubviews could leave that page detached. The pages now wait
|
||||||
|
// for the measured width. No React Native renderer in the test env, so this
|
||||||
|
// checks the wiring.
|
||||||
|
|
||||||
|
const src = fs.readFileSync(path.resolve(__dirname, '..', '..', 'screens/EmailThreadScreen.tsx'), 'utf8');
|
||||||
|
|
||||||
|
describe('reader pager width', () => {
|
||||||
|
it('renders the pages only once the pager is measured', () => {
|
||||||
|
expect(src).toMatch(/<View style=\{styles\.pagerViewport\} onLayout=\{onPagerLayout\}>\s+\{measuredWidth > 0 && \(\s+<FlatList/);
|
||||||
|
expect(src).toMatch(/const pageWidth = measuredWidth \|\| windowWidth;/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not clip the pages away', () => {
|
||||||
|
const pager = src.slice(src.indexOf('<FlatList'), src.indexOf('renderItem={({ item, index })'));
|
||||||
|
expect(pager).not.toMatch(/^\s+removeClippedSubviews\s*$/m);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('re-renders the pages when their width changes', () => {
|
||||||
|
expect(src).toMatch(/\(\) => \[cacheVersion, neighboursReady, pageWidth\]/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -119,8 +119,13 @@ function EmailViewer({ route, navigation, embedded = false, onActiveChange }: Pr
|
|||||||
const { width: windowWidth } = useWindowDimensions();
|
const { width: windowWidth } = useWindowDimensions();
|
||||||
// Pages are as wide as the pager itself, which is the window on a phone
|
// Pages are as wide as the pager itself, which is the window on a phone
|
||||||
// but only the detail pane in a split view. Measured, so a rotation or a
|
// but only the detail pane in a split view. Measured, so a rotation or a
|
||||||
// pane resize re-sizes them.
|
// pane resize re-sizes them. The pages render only once it is measured:
|
||||||
const [pageWidth, setPageWidth] = React.useState(windowWidth);
|
// laid out at the window's width first, a split view's pager scrolled the
|
||||||
|
// opened message to the wrong offset, and on a tablet the pane once stayed
|
||||||
|
// empty (just the toolbar and the bottom bar) until the message was
|
||||||
|
// reopened.
|
||||||
|
const [measuredWidth, setMeasuredWidth] = React.useState(0);
|
||||||
|
const pageWidth = measuredWidth || windowWidth;
|
||||||
const markRead = useEmailStore((s) => s.markRead);
|
const markRead = useEmailStore((s) => s.markRead);
|
||||||
const setKeywordForEmails = useEmailStore((s) => s.setKeywordForEmails);
|
const setKeywordForEmails = useEmailStore((s) => s.setKeywordForEmails);
|
||||||
const deleteEmail = useEmailStore((s) => s.deleteEmail);
|
const deleteEmail = useEmailStore((s) => s.deleteEmail);
|
||||||
@@ -336,12 +341,12 @@ function EmailViewer({ route, navigation, embedded = false, onActiveChange }: Pr
|
|||||||
}, [pageWidth]);
|
}, [pageWidth]);
|
||||||
const onPagerLayout = React.useCallback((e: LayoutChangeEvent) => {
|
const onPagerLayout = React.useCallback((e: LayoutChangeEvent) => {
|
||||||
const width = Math.round(e.nativeEvent.layout.width);
|
const width = Math.round(e.nativeEvent.layout.width);
|
||||||
if (width > 0) setPageWidth((prev) => (prev === width ? prev : width));
|
if (width > 0) setMeasuredWidth((prev) => (prev === width ? prev : width));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const pagerExtraData = React.useMemo(
|
const pagerExtraData = React.useMemo(
|
||||||
() => [cacheVersion, neighboursReady],
|
() => [cacheVersion, neighboursReady, pageWidth],
|
||||||
[cacheVersion, neighboursReady],
|
[cacheVersion, neighboursReady, pageWidth],
|
||||||
);
|
);
|
||||||
|
|
||||||
// While the body is pinch-zoomed the pager must not treat horizontal
|
// While the body is pinch-zoomed the pager must not treat horizontal
|
||||||
@@ -815,10 +820,11 @@ function EmailViewer({ route, navigation, embedded = false, onActiveChange }: Pr
|
|||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
<View style={styles.pagerViewport} onLayout={onPagerLayout}>
|
||||||
|
{measuredWidth > 0 && (
|
||||||
<FlatList
|
<FlatList
|
||||||
ref={listRef}
|
ref={listRef}
|
||||||
style={styles.pagerViewport}
|
style={styles.pagerViewport}
|
||||||
onLayout={onPagerLayout}
|
|
||||||
onContentSizeChange={snapToActive}
|
onContentSizeChange={snapToActive}
|
||||||
data={emails}
|
data={emails}
|
||||||
extraData={pagerExtraData}
|
extraData={pagerExtraData}
|
||||||
@@ -831,7 +837,8 @@ function EmailViewer({ route, navigation, embedded = false, onActiveChange }: Pr
|
|||||||
windowSize={3}
|
windowSize={3}
|
||||||
initialNumToRender={1}
|
initialNumToRender={1}
|
||||||
maxToRenderPerBatch={2}
|
maxToRenderPerBatch={2}
|
||||||
removeClippedSubviews
|
// Not removeClippedSubviews: with three pages it saves nothing,
|
||||||
|
// and Android can leave the open page detached after a resize.
|
||||||
scrollEnabled={!pagerLocked && kbObstruction === 0}
|
scrollEnabled={!pagerLocked && kbObstruction === 0}
|
||||||
onScrollBeginDrag={releaseNeighbours}
|
onScrollBeginDrag={releaseNeighbours}
|
||||||
onMomentumScrollEnd={onMomentumEnd}
|
onMomentumScrollEnd={onMomentumEnd}
|
||||||
@@ -876,6 +883,8 @@ function EmailViewer({ route, navigation, embedded = false, onActiveChange }: Pr
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Bottom action bar (hidden while keyboard is up so quick reply stays visible) */}
|
{/* Bottom action bar (hidden while keyboard is up so quick reply stays visible) */}
|
||||||
{kbObstruction === 0 && (
|
{kbObstruction === 0 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user