mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-03 14:41:26 +03:00
avatar (#14440)
* avatar
* refactor avatar display: unify rendering and resolve at use time
- Extract buildAvatarWidget() in common.dart to share avatar rendering
logic across desktop settings, desktop CM and mobile CM
- Add resolve_avatar_url() in Rust, exposed via FFI (SyncReturn),
to resolve relative avatar paths (e.g. "/avatar/xxx") to absolute URLs
- Store avatar as-is in local config, only resolve when displaying
(settings page) or sending (LoginRequest)
- Resolve avatar in LoginRequest before sending to remote peer
- Add error handling for network image load failures
- Guard against empty client.name[0] crash
- Show avatar in mobile settings page account tile
Signed-off-by: 21pages <sunboeasy@gmail.com>
* web: implement mainResolveAvatarUrl via js getByName
Signed-off-by: 21pages <sunboeasy@gmail.com>
* increase ipc Data enum size limit to 120 bytes
Signed-off-by: 21pages <sunboeasy@gmail.com>
---------
Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
@@ -2026,28 +2026,65 @@ class _AccountState extends State<_Account> {
|
||||
}
|
||||
|
||||
Widget useInfo() {
|
||||
text(String key, String value) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SelectionArea(child: Text('${translate(key)}: $value'))
|
||||
.marginSymmetric(vertical: 4),
|
||||
);
|
||||
}
|
||||
|
||||
return Obx(() => Offstage(
|
||||
offstage: gFFI.userModel.userName.value.isEmpty,
|
||||
child: Column(
|
||||
children: [
|
||||
if (gFFI.userModel.displayName.value.trim().isNotEmpty &&
|
||||
gFFI.userModel.displayName.value.trim() !=
|
||||
gFFI.userModel.userName.value.trim())
|
||||
text('Display Name', gFFI.userModel.displayName.value.trim()),
|
||||
text('Username', gFFI.userModel.userName.value),
|
||||
// text('Group', gFFI.groupModel.groupName.value),
|
||||
],
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Builder(builder: (context) {
|
||||
final avatarWidget = _buildUserAvatar();
|
||||
return Row(
|
||||
children: [
|
||||
if (avatarWidget != null) avatarWidget,
|
||||
if (avatarWidget != null) const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
gFFI.userModel.displayNameOrUserName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
SelectionArea(
|
||||
child: Text(
|
||||
'@${gFFI.userModel.userName.value}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color:
|
||||
Theme.of(context).textTheme.bodySmall?.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
)).marginOnly(left: 18, top: 16);
|
||||
}
|
||||
|
||||
Widget? _buildUserAvatar() {
|
||||
// Resolve relative avatar path at display time
|
||||
final avatar =
|
||||
bind.mainResolveAvatarUrl(avatar: gFFI.userModel.avatar.value);
|
||||
return buildAvatarWidget(
|
||||
avatar: avatar,
|
||||
size: 44,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Checkbox extends StatefulWidget {
|
||||
|
||||
@@ -462,23 +462,7 @@ class _CmHeaderState extends State<_CmHeader>
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 70,
|
||||
height: 70,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: str2color(client.name),
|
||||
borderRadius: BorderRadius.circular(15.0),
|
||||
),
|
||||
child: Text(
|
||||
client.name[0],
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
fontSize: 55,
|
||||
),
|
||||
),
|
||||
).marginOnly(right: 10.0),
|
||||
_buildClientAvatar().marginOnly(right: 10.0),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
@@ -582,6 +566,35 @@ class _CmHeaderState extends State<_CmHeader>
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
Widget _buildClientAvatar() {
|
||||
return buildAvatarWidget(
|
||||
avatar: client.avatar,
|
||||
size: 70,
|
||||
borderRadius: 15,
|
||||
fallback: _buildInitialAvatar(),
|
||||
)!;
|
||||
}
|
||||
|
||||
Widget _buildInitialAvatar() {
|
||||
return Container(
|
||||
width: 70,
|
||||
height: 70,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: str2color(client.name),
|
||||
borderRadius: BorderRadius.circular(15.0),
|
||||
),
|
||||
child: Text(
|
||||
client.name.isNotEmpty ? client.name[0] : '?',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
fontSize: 55,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PrivilegeBoard extends StatefulWidget {
|
||||
|
||||
Reference in New Issue
Block a user