Files
tongxunruanjian/mobile/lib/utils/format.dart
T
KIMI b95159f7eb 新增 mobile/:畅联手机端 Flutter 工程(B-58)
- 登录:工号+密码走 account-service /api/login,自动登录、被踢下线回登录页
- 消息:会话列表(未读角标/免打扰/时间)、单聊群聊、文字/语音/图片/文件消息、失败重发、历史分页
- 通讯录:新的同事/我的群聊入口、按部门分组(好友 ex 字段)、搜索
- 通话:一对一语音通话(信令走 OpenIM 自定义消息 + LiveKit,token 走 /api/rtc_token)
- 界面按确认效果图 m1/m2/m3 实现,主色 #3B87F5,四态视图齐全,无英文残留
2026-08-09 01:28:12 +08:00

122 lines
4.2 KiB
Dart

import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
/// 时间与消息预览的格式化工具,全部用户可见文案集中在这里,避免出现英文残留。
class FormatUtils {
FormatUtils._();
static const List<String> _weekdays = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'];
static String _two(int v) => v.toString().padLeft(2, '0');
/// 消息列表右侧时间:当天 HH:mm、昨天、一周内星期X、更早显示日期
static String conversationTime(int? millis) {
if (millis == null || millis <= 0) return '';
final t = DateTime.fromMillisecondsSinceEpoch(millis);
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final day = DateTime(t.year, t.month, t.day);
final diff = today.difference(day).inDays;
if (diff <= 0) {
return '${t.hour}:${_two(t.minute)}';
} else if (diff == 1) {
return '昨天';
} else if (diff < 7) {
return _weekdays[t.weekday - 1];
} else if (t.year == now.year) {
return '${t.month}${t.day}日';
}
return '${t.year}${t.month}${t.day}日';
}
/// 聊天页时间分隔条:当天「上午 9:30」,非当天带日期
static String chatDividerTime(int millis) {
final t = DateTime.fromMillisecondsSinceEpoch(millis);
final now = DateTime.now();
final isToday = t.year == now.year && t.month == now.month && t.day == now.day;
final period = t.hour < 6
? '凌晨'
: t.hour < 12
? '上午'
: t.hour < 13
? '中午'
: t.hour < 18
? '下午'
: '晚上';
final hour12 = t.hour % 12 == 0 ? 12 : t.hour % 12;
final clock = '$period $hour12:${_two(t.minute)}';
if (isToday) return clock;
if (t.year == now.year) return '${t.month}${t.day}$clock';
return '${t.year}${t.month}${t.day}$clock';
}
/// 通话时长:mm:ss
static String callDuration(int seconds) {
final m = seconds ~/ 60;
final s = seconds % 60;
return '${_two(m)}:${_two(s)}';
}
/// 文件大小:不足 1 KB 显示 B,不足 1 MB 显示 KB,否则 MB
static String fileSize(int? bytes) {
if (bytes == null || bytes < 0) return '';
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(0)} KB';
return '${(bytes / 1024 / 1024).toStringAsFixed(1)} MB';
}
/// 会话预览文本:群消息带「发送人: 」前缀,语音/文件等按效果图规则
static String messagePreview(Message? msg, {required bool isGroup}) {
if (msg == null) return '';
String body;
switch (msg.contentType) {
case MessageType.text:
case MessageType.atText:
case MessageType.quote:
body = msg.textElem?.content ?? '';
break;
case MessageType.voice:
body = '[语音]';
break;
case MessageType.picture:
body = '[图片]';
break;
case MessageType.video:
body = '[视频]';
break;
case MessageType.file:
final name = msg.fileElem?.fileName ?? '';
body = name.isEmpty ? '[文件]' : '[文件] $name';
break;
case MessageType.custom:
body = _customPreview(msg);
break;
default:
if ((msg.contentType ?? 0) >= MessageType.notificationBegin) {
body = '[群通知]';
} else {
body = '[其他消息]';
}
}
// 群聊里别人发的消息,预览加「发送人: 」前缀
if (isGroup && msg.sendID != OpenIM.iMManager.userID && body.isNotEmpty) {
final sender = msg.senderNickname ?? '';
if (sender.isNotEmpty) return '$sender: $body';
}
return body;
}
static String _customPreview(Message msg) {
try {
final data = msg.customElem?.data;
if (data == null || data.isEmpty) return '[其他消息]';
// 通话相关的自定义消息(信令 200-204、通话记录 901)在会话里统一显示 [语音通话]
if (data.contains('calling') || data.contains('"customType":901')) {
return '[语音通话]';
}
} catch (_) {
// 解析失败按未知消息处理
}
return '[其他消息]';
}
}