- 登录:工号+密码走 account-service /api/login,自动登录、被踢下线回登录页 - 消息:会话列表(未读角标/免打扰/时间)、单聊群聊、文字/语音/图片/文件消息、失败重发、历史分页 - 通讯录:新的同事/我的群聊入口、按部门分组(好友 ex 字段)、搜索 - 通话:一对一语音通话(信令走 OpenIM 自定义消息 + LiveKit,token 走 /api/rtc_token) - 界面按确认效果图 m1/m2/m3 实现,主色 #3B87F5,四态视图齐全,无英文残留
122 lines
4.2 KiB
Dart
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 '[其他消息]';
|
|
}
|
|
}
|