更新 AuthApi 枚举以添加获取推流地址的 API 路径;重构 Auth 页面以优化输入控件和按钮布局;更新扫码页面以导航到录制页面。

This commit is contained in:
2026-07-09 14:32:39 +08:00
parent 9766c43722
commit c38f6d69c8
5 changed files with 278 additions and 39 deletions
+234
View File
@@ -0,0 +1,234 @@
import 'package:flutter/material.dart';
/// 全局文本尺寸语义。
///
/// 业务代码应优先选择语义化的 variant,而不是直接散落 `fontSize`。
/// 这样后续调整整套字号体系时,只需要维护这一处映射。
enum AppTextVariant {
display,
headline,
title,
subtitle,
body,
bodySmall,
label,
caption,
}
/// 全局文本颜色语义。
///
/// tone 只表达“文本在界面中的语义角色”,具体颜色从当前 Theme 解析,
/// 避免页面直接依赖硬编码色值。
enum AppTextTone {
primary,
secondary,
tertiary,
inverse,
brand,
success,
warning,
danger,
disabled,
}
/// 应用级文本组件。
///
/// `AppText` 是对 Flutter `Text` 的轻量封装,目标是统一页面里的字号、
/// 字重、颜色和溢出策略,同时保留 `Text` 的常用能力。
///
/// 使用建议:
/// - 普通文案使用默认 `AppText('内容')`。
/// - 标题使用 `variant: AppTextVariant.title`。
/// - 错误、警告、成功等状态文案使用 `tone`,不要在业务里直接写颜色。
/// - 只有遇到一次性视觉细节时才传入 `style`、`fontSize` 或 `fontWeight`。
class AppText extends StatelessWidget {
const AppText(
this.data, {
super.key,
this.variant = AppTextVariant.body,
this.tone = AppTextTone.primary,
this.style,
this.color,
this.fontSize,
this.fontWeight,
this.height,
this.letterSpacing,
this.textAlign,
this.textDirection,
this.locale,
this.softWrap,
this.overflow,
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
this.textHeightBehavior,
this.textScaler,
this.selectionColor,
}) : textSpan = null;
/// 富文本构造器。
///
/// 用于同一段文案中存在局部强调、不同颜色或不同字重的场景。
/// 外层的 `variant`、`tone` 和通用排版参数仍会作为默认样式作用到 span。
const AppText.rich(
this.textSpan, {
super.key,
this.variant = AppTextVariant.body,
this.tone = AppTextTone.primary,
this.style,
this.color,
this.fontSize,
this.fontWeight,
this.height,
this.letterSpacing,
this.textAlign,
this.textDirection,
this.locale,
this.softWrap,
this.overflow,
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
this.textHeightBehavior,
this.textScaler,
this.selectionColor,
}) : data = null;
/// 普通文本内容。与 [textSpan] 二选一。
final String? data;
/// 富文本内容。与 [data] 二选一。
final InlineSpan? textSpan;
/// 文本尺寸和基础字重语义。
final AppTextVariant variant;
/// 文本颜色语义。
final AppTextTone tone;
/// 额外样式覆盖。优先级高于 variant 和 tone。
final TextStyle? style;
/// 显式颜色覆盖。优先级高于 tone 和 `style.color`。
final Color? color;
/// 一次性字号覆盖。常规场景优先使用 [variant]。
final double? fontSize;
/// 一次性字重覆盖。常规场景优先使用 [variant]。
final FontWeight? fontWeight;
/// 行高覆盖。
final double? height;
/// 字间距覆盖。
final double? letterSpacing;
final TextAlign? textAlign;
final TextDirection? textDirection;
final Locale? locale;
final bool? softWrap;
final TextOverflow? overflow;
final int? maxLines;
final String? semanticsLabel;
final TextWidthBasis? textWidthBasis;
final TextHeightBehavior? textHeightBehavior;
final TextScaler? textScaler;
final Color? selectionColor;
@override
Widget build(BuildContext context) {
final effectiveStyle = _resolveStyle(context);
if (textSpan != null) {
return Text.rich(
textSpan!,
style: effectiveStyle,
textAlign: textAlign,
textDirection: textDirection,
locale: locale,
softWrap: softWrap,
overflow: overflow,
maxLines: maxLines,
semanticsLabel: semanticsLabel,
textWidthBasis: textWidthBasis,
textHeightBehavior: textHeightBehavior,
textScaler: textScaler,
selectionColor: selectionColor,
);
}
return Text(
data ?? '',
style: effectiveStyle,
textAlign: textAlign,
textDirection: textDirection,
locale: locale,
softWrap: softWrap,
overflow: overflow,
maxLines: maxLines,
semanticsLabel: semanticsLabel,
textWidthBasis: textWidthBasis,
textHeightBehavior: textHeightBehavior,
textScaler: textScaler,
selectionColor: selectionColor,
);
}
/// 合成最终样式。
///
/// 优先级从低到高:
/// 1. Theme 中的 TextTheme。
/// 2. `variant` 与 `tone` 对应的默认样式。
/// 3. 外部传入的 `style`。
/// 4. `color`、`fontSize`、`fontWeight` 等显式字段。
TextStyle _resolveStyle(BuildContext context) {
final baseStyle = _variantStyle(Theme.of(context).textTheme);
final toneStyle = baseStyle.copyWith(color: _toneColor(context));
final mergedStyle = style == null ? toneStyle : toneStyle.merge(style);
return mergedStyle.copyWith(
color: color ?? mergedStyle.color,
fontSize: fontSize ?? mergedStyle.fontSize,
fontWeight: fontWeight ?? mergedStyle.fontWeight,
height: height ?? mergedStyle.height,
letterSpacing: letterSpacing ?? mergedStyle.letterSpacing,
);
}
TextStyle _variantStyle(TextTheme textTheme) {
return switch (variant) {
AppTextVariant.display =>
textTheme.displaySmall ?? const TextStyle(fontSize: 36),
AppTextVariant.headline =>
textTheme.headlineSmall ?? const TextStyle(fontSize: 24),
AppTextVariant.title =>
textTheme.titleMedium ?? const TextStyle(fontSize: 16),
AppTextVariant.subtitle =>
textTheme.titleSmall ?? const TextStyle(fontSize: 14),
AppTextVariant.body =>
textTheme.bodyMedium ?? const TextStyle(fontSize: 14),
AppTextVariant.bodySmall =>
textTheme.bodySmall ?? const TextStyle(fontSize: 12),
AppTextVariant.label =>
textTheme.labelLarge ?? const TextStyle(fontSize: 14),
AppTextVariant.caption =>
textTheme.labelSmall ?? const TextStyle(fontSize: 11),
};
}
Color _toneColor(BuildContext context) {
final colors = Theme.of(context).colorScheme;
return switch (tone) {
AppTextTone.primary => colors.onSurface,
AppTextTone.secondary => colors.onSurfaceVariant,
AppTextTone.tertiary => colors.outline,
AppTextTone.inverse => colors.onInverseSurface,
AppTextTone.brand => colors.primary,
AppTextTone.success => colors.tertiary,
AppTextTone.warning => colors.secondary,
AppTextTone.danger => colors.error,
AppTextTone.disabled => colors.onSurface.withValues(alpha: 0.38),
};
}
}