1. 修复安卓部分机型顶部安全区遮挡回忆录标题的问题;
2. 降低封面图生成阈值和展示逻辑,独立封面图未生成时,使用正文图;
3. 去掉“嗯。”生硬回答,去掉不合理段首承接词;
4. 新增章节封面所需最少插图数的配置项
This commit is contained in:
yangshilin
2026-04-16 20:42:54 +08:00
parent 17b9fa3466
commit 9af2060259
15 changed files with 377 additions and 74 deletions

View File

@@ -18,8 +18,13 @@ import { useTranslation } from 'react-i18next';
import { Icon } from '@/components/ui/icon';
import { Text } from '@/components/ui/text';
import { ScreenHeader } from '@/components/screen-header';
import {
getScreenHeaderLayoutMetrics,
ScreenHeader,
} from '@/components/screen-header';
import { ScreenGutter } from '@/constants/layout';
import { useTypography } from '@/core/typography-context';
import { useAppSettings } from '@/hooks/use-app-settings';
import {
MarkdownRenderer,
ReadingMarkdownHorizontalRuleInColumn,
@@ -353,6 +358,8 @@ function ReadingSettingsModal({
export default function ChapterScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const insets = useSafeAreaInsets();
const { largeText } = useAppSettings();
const typography = useTypography();
const { width } = useWindowDimensions();
const { t } = useTranslation('memoir');
const { data: chapter, isLoading } = useChapterDetail(id ?? '');
@@ -429,8 +436,13 @@ export default function ChapterScreen() {
const useReadingSegments =
Array.isArray(readingSegments) && readingSegments.length > 0;
/** 与 ScreenHeaderreading、useSafeArea可视高度对齐,避免返回栏与首屏内容之间出现空隙 */
const headerOccupiedHeight = Math.max(insets.top, 12) + 56;
/** 与 ScreenHeaderreading、useSafeArea实际总高度一致,避免章节标题被顶栏或安全区遮挡 */
const headerOccupiedHeight = getScreenHeaderLayoutMetrics(insets, {
useSafeArea: true,
variant: 'reading',
largeText,
typography,
}).totalHeight;
const handleDeletePress = () => {
Alert.alert(

View File

@@ -9,6 +9,8 @@ import { useTypography } from '@/core/typography-context';
import { ScreenGutter } from '@/constants/layout';
import { useAppSettings } from '@/hooks/use-app-settings';
import type { TypographyTokens } from '@/core/typography-context';
/** 默认最小触控目标 48dp大字模式下与标题字号匹配略向左扩展便于够到边缘 */
const BACK_HIT_MIN = 48;
const BACK_HIT_MIN_LARGE = 56;
@@ -18,6 +20,52 @@ const BACK_EXTRA_HIT_LEFT = 4;
export type ScreenHeaderVariant = 'default' | 'chat' | 'reading';
export type ScreenHeaderLayoutOpts = {
useSafeArea: boolean;
variant: ScreenHeaderVariant;
largeText: boolean;
typography: TypographyTokens;
};
/**
* 与组件内布局一致的总高度(含顶部安全区内边距),供绝对定位顶栏下的 ScrollView paddingTop 等使用。
*/
export function getScreenHeaderLayoutMetrics(
insets: { top: number },
opts: ScreenHeaderLayoutOpts,
) {
const barPaddingBottom = opts.largeText ? 18 : 16;
const titleRowPaddingV = opts.largeText ? 8 : 4;
const backTouchMin = opts.largeText ? BACK_HIT_MIN_LARGE : BACK_HIT_MIN;
const { variant, typography, largeText } = opts;
const titleFontSize =
variant === 'chat'
? largeText
? typography.headingMedium
: typography.headingSmall
: Math.max(typography.titleLarge, typography.headingSmall);
const titleLineMin =
variant === 'chat'
? Math.ceil(titleFontSize * (largeText ? 1.45 : 1.38)) +
(largeText ? 14 : 10)
: Math.ceil(titleFontSize * 1.32) + (largeText ? 10 : 8);
const titleRowMinHeight = Math.max(backTouchMin, titleLineMin);
const titleRowOuterHeight = titleRowMinHeight + 2 * titleRowPaddingV;
const paddingTop = opts.useSafeArea ? Math.max(insets.top, 12) : 12;
const totalHeight = paddingTop + titleRowOuterHeight + barPaddingBottom;
return {
barPaddingBottom,
titleRowPaddingV,
titleFontSize,
titleLineMin,
titleRowMinHeight,
titleRowOuterHeight,
paddingTop,
totalHeight,
backTouchMin,
};
}
const VARIANT_COLORS = {
default: {
title: undefined, // use theme foreground
@@ -76,7 +124,6 @@ export function ScreenHeader({
const typography = useTypography();
const colors = VARIANT_COLORS[variant];
const backTouchMin = largeText ? BACK_HIT_MIN_LARGE : BACK_HIT_MIN;
const backIconSize = largeText ? BACK_ICON_SIZE_LARGE : BACK_ICON_SIZE;
const handleBack = onBack ?? (() => router.back());
@@ -88,27 +135,26 @@ export function ScreenHeader({
* 不要用外层 minHeight「框死」整块栏paddingTop 含安全区时会把内容区压扁。
* 标题行最小高度随当前排版 token 计算(关大字也要留够,避免裁字 / Android font padding
*/
const barPaddingBottom = largeText ? 18 : 16;
const titleRowPaddingV = largeText ? 8 : 4;
const titleFontSize =
variant === 'chat'
? largeText
? typography.headingMedium
: typography.headingSmall
: Math.max(typography.titleLarge, typography.headingSmall);
const titleLineMin =
variant === 'chat'
? Math.ceil(titleFontSize * (largeText ? 1.45 : 1.38)) +
(largeText ? 14 : 10)
: Math.ceil(titleFontSize * 1.32) + (largeText ? 10 : 8);
const titleRowMinHeight = Math.max(backTouchMin, titleLineMin);
const {
barPaddingBottom,
titleRowPaddingV,
titleFontSize,
titleRowMinHeight,
paddingTop: headerPaddingTop,
backTouchMin,
} = getScreenHeaderLayoutMetrics(insets, {
useSafeArea,
variant,
largeText,
typography,
});
const containerStyle = {
flexDirection: 'row' as const,
alignItems: 'center' as const,
justifyContent: 'space-between' as const,
paddingHorizontal: Math.max(ScreenGutter, 16),
paddingTop: useSafeArea ? Math.max(insets.top, 12) : 12,
paddingTop: headerPaddingTop,
paddingBottom: barPaddingBottom,
...(bgColor && { backgroundColor: bgColor }),
...(absolute && {