此提交包含多项变更: 1. 新增鸿蒙平台支持,完善设备检测与数据库适配 2. 替换旧版分享插件API为SharePlus 3. 批量迁移StateNotifier到Notifier以适配新版Riverpod 4. 修复zip编码判断、图表API参数等bug 5. 更新应用图标、启动页资源与多尺寸适配图标 6. 调整Android最小SDK版本与应用名称 7. 优化日志打印与正则表达式使用 8. 修正编辑器画布样式初始化与配置逻辑 9. 更新依赖与CI插件配置
166 lines
5.0 KiB
Dart
166 lines
5.0 KiB
Dart
/// ============================================================
|
||
/// 闲言APP — 通用扩展方法
|
||
/// 创建时间: 2026-04-20
|
||
/// 更新时间: 2026-05-16
|
||
/// 作用: String / BuildContext / Color 等常用扩展
|
||
/// 上次更新: RegExp() → regex() 消除 Dart 3.11 废弃警告
|
||
/// ============================================================
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:xianyan/core/utils/pattern_utils.dart';
|
||
|
||
import 'device_detection.dart';
|
||
|
||
// ============================================================
|
||
// String 扩展
|
||
// ============================================================
|
||
|
||
/// 字符串扩展方法
|
||
extension StringX on String {
|
||
/// 是否为空白 (null 或纯空格)
|
||
bool get isBlank => trim().isEmpty;
|
||
|
||
/// 是否非空白
|
||
bool get isNotBlank => trim().isNotEmpty;
|
||
|
||
/// 截断到指定长度
|
||
String ellipsis(int maxLength, {String suffix = '…'}) {
|
||
if (length <= maxLength) return this;
|
||
return '${substring(0, maxLength)}$suffix';
|
||
}
|
||
|
||
/// 首字母大写
|
||
String get capitalize {
|
||
if (isEmpty) return this;
|
||
return '${this[0].toUpperCase()}${substring(1)}';
|
||
}
|
||
|
||
/// 去除HTML标签,保留纯文本
|
||
String get stripHtml {
|
||
if (isEmpty) return this;
|
||
var result = this;
|
||
result = result.replaceAll(regex(r'<br\s*/?>', caseSensitive: false), '\n');
|
||
result = result.replaceAll(regex(r'<p\s*/?>', caseSensitive: false), '\n');
|
||
result = result.replaceAll(regex(r'</p>', caseSensitive: false), '');
|
||
result = result.replaceAll(
|
||
regex(r'<strong[^>]*>(.*?)</strong>', caseSensitive: false, dotAll: true),
|
||
r'$1',
|
||
);
|
||
result = result.replaceAll(
|
||
regex(r'<em[^>]*>(.*?)</em>', caseSensitive: false, dotAll: true),
|
||
r'$1',
|
||
);
|
||
result = result.replaceAll(regex(r'<[^>]*>'), '');
|
||
result = result.replaceAll(regex(r'\n{3,}'), '\n\n');
|
||
return result.trim();
|
||
}
|
||
|
||
/// 解码HTML实体
|
||
String get decodeHtmlEntities {
|
||
if (isEmpty) return this;
|
||
return replaceAll('&', '&')
|
||
.replaceAll('<', '<')
|
||
.replaceAll('>', '>')
|
||
.replaceAll('"', '"')
|
||
.replaceAll(''', "'")
|
||
.replaceAll(' ', ' ');
|
||
}
|
||
|
||
/// 去除HTML标签 + 解码HTML实体
|
||
String get cleanHtml => stripHtml.decodeHtmlEntities;
|
||
}
|
||
|
||
// ============================================================
|
||
// BuildContext 扩展
|
||
// ============================================================
|
||
|
||
/// BuildContext 扩展方法
|
||
extension BuildContextX on BuildContext {
|
||
/// 获取主题
|
||
ThemeData get theme => Theme.of(this);
|
||
|
||
/// 获取颜色方案
|
||
ColorScheme get colorScheme => Theme.of(this).colorScheme;
|
||
|
||
/// 获取 TextTheme
|
||
TextTheme get textTheme => Theme.of(this).textTheme;
|
||
|
||
/// 获取屏幕尺寸
|
||
Size get screenSize => MediaQuery.sizeOf(this);
|
||
|
||
/// 获取屏幕宽度
|
||
double get screenWidth => screenSize.width;
|
||
|
||
/// 获取屏幕高度
|
||
double get screenHeight => screenSize.height;
|
||
|
||
/// 获取底部安全区域
|
||
double get bottomPadding => MediaQuery.paddingOf(this).bottom;
|
||
|
||
/// 获取顶部安全区域
|
||
double get topPadding => MediaQuery.paddingOf(this).top;
|
||
|
||
/// 是否为小屏 (< mobile断点)
|
||
bool get isSmallScreen => screenWidth < Breakpoints.mobile;
|
||
|
||
/// 是否为中屏 (mobile - desktop断点)
|
||
bool get isMediumScreen =>
|
||
screenWidth >= Breakpoints.mobile && screenWidth < Breakpoints.desktop;
|
||
|
||
/// 是否为大屏 (>= desktop断点)
|
||
bool get isLargeScreen => screenWidth >= Breakpoints.desktop;
|
||
|
||
/// 是否横屏
|
||
bool get isLandscape => screenWidth > screenHeight;
|
||
|
||
/// 是否竖屏
|
||
bool get isPortrait => screenWidth < screenHeight;
|
||
|
||
/// 屏幕尺寸分类
|
||
ScreenSizeClass get sizeClass =>
|
||
DeviceDetection.sizeClassFromWidth(screenWidth);
|
||
|
||
/// 布局列数
|
||
int get layoutColumns => sizeClass.layoutColumns;
|
||
}
|
||
|
||
// ============================================================
|
||
// Color 扩展
|
||
// ============================================================
|
||
|
||
/// Color 扩展方法
|
||
extension ColorX on Color {
|
||
/// 调整透明度 (0.0 - 1.0)
|
||
Color withAlpha(double alpha) {
|
||
return withValues(alpha: alpha.clamp(0.0, 1.0));
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// DateTime 扩展
|
||
// ============================================================
|
||
|
||
/// DateTime 扩展方法
|
||
extension DateTimeX on DateTime {
|
||
/// 友好时间文本 (如 "3分钟前")
|
||
String get timeAgo {
|
||
final now = DateTime.now();
|
||
final diff = now.difference(this);
|
||
|
||
if (diff.inSeconds < 60) return '刚刚';
|
||
if (diff.inMinutes < 60) return '${diff.inMinutes}分钟前';
|
||
if (diff.inHours < 24) return '${diff.inHours}小时前';
|
||
if (diff.inDays < 30) return '${diff.inDays}天前';
|
||
if (diff.inDays < 365) return '${diff.inDays ~/ 30}个月前';
|
||
return '${diff.inDays ~/ 365}年前';
|
||
}
|
||
|
||
/// 格式化日期 yyyy-MM-dd
|
||
String get formatDate =>
|
||
'$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
|
||
|
||
/// 格式化时间 HH:mm
|
||
String get formatTime =>
|
||
'${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}';
|
||
}
|