- 登录:工号+密码走 account-service /api/login,自动登录、被踢下线回登录页 - 消息:会话列表(未读角标/免打扰/时间)、单聊群聊、文字/语音/图片/文件消息、失败重发、历史分页 - 通讯录:新的同事/我的群聊入口、按部门分组(好友 ex 字段)、搜索 - 通话:一对一语音通话(信令走 OpenIM 自定义消息 + LiveKit,token 走 /api/rtc_token) - 界面按确认效果图 m1/m2/m3 实现,主色 #3B87F5,四态视图齐全,无英文残留
103 lines
2.7 KiB
Dart
103 lines
2.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../theme.dart';
|
|
|
|
/// 头像(按效果图):
|
|
/// - 单人是圆角方块 + 姓氏首字,蓝 / 灰蓝两色按名字 hash 取色;
|
|
/// - 群聊是 2x2 小方块拼格;
|
|
/// - 有 faceURL 时优先显示网络头像,加载失败回退到首字。
|
|
class NameAvatar extends StatelessWidget {
|
|
final String name;
|
|
final String? faceURL;
|
|
final double size;
|
|
|
|
/// 是否群聊样式(2x2 拼格)
|
|
final bool isGroup;
|
|
|
|
const NameAvatar({
|
|
super.key,
|
|
required this.name,
|
|
this.faceURL,
|
|
this.size = 48,
|
|
this.isGroup = false,
|
|
});
|
|
|
|
Color _colorFor(String text) {
|
|
var hash = 0;
|
|
for (final code in text.codeUnits) {
|
|
hash = (hash * 31 + code) & 0x7fffffff;
|
|
}
|
|
return hash % 2 == 0 ? AppColors.primary : AppColors.avatarGray;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final radius = BorderRadius.circular(size * 0.18);
|
|
if (isGroup) return _groupAvatar(radius);
|
|
|
|
final hasFace = faceURL != null && faceURL!.isNotEmpty;
|
|
final letter = name.isNotEmpty ? name.substring(0, 1) : '';
|
|
|
|
return ClipRRect(
|
|
borderRadius: radius,
|
|
child: Container(
|
|
width: size,
|
|
height: size,
|
|
color: _colorFor(name),
|
|
alignment: Alignment.center,
|
|
child: hasFace
|
|
? Image.network(
|
|
faceURL!,
|
|
width: size,
|
|
height: size,
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (_, __, ___) => _letterText(letter),
|
|
loadingBuilder: (_, child, progress) => progress == null ? child : _letterText(letter),
|
|
)
|
|
: _letterText(letter),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _letterText(String letter) {
|
|
return Text(
|
|
letter,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: size * 0.4,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
);
|
|
}
|
|
|
|
/// 群头像:浅灰底上 2x2 小方块,蓝 / 灰蓝交替
|
|
Widget _groupAvatar(BorderRadius radius) {
|
|
final cell = (size - 10) / 2;
|
|
const colors = [AppColors.primary, AppColors.avatarGray, AppColors.avatarGray, AppColors.primary];
|
|
return ClipRRect(
|
|
borderRadius: radius,
|
|
child: Container(
|
|
width: size,
|
|
height: size,
|
|
color: AppColors.searchBg,
|
|
padding: const EdgeInsets.all(2),
|
|
child: Wrap(
|
|
spacing: 2,
|
|
runSpacing: 2,
|
|
children: List.generate(
|
|
4,
|
|
(i) => Container(
|
|
width: cell,
|
|
height: cell,
|
|
decoration: BoxDecoration(
|
|
color: colors[i],
|
|
borderRadius: BorderRadius.circular(2),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|