diff --git a/lib/common/widgets/avatars.dart b/lib/common/widgets/avatars.dart new file mode 100644 index 000000000..7444a0206 --- /dev/null +++ b/lib/common/widgets/avatars.dart @@ -0,0 +1,54 @@ +import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/models/model_owner.dart'; +import 'package:flutter/material.dart'; + +Widget avatars({ + required ColorScheme colorScheme, + required Iterable users, +}) { + const gap = 6.0; + const size = 22.0; + const offset = size - gap; + if (users.length == 1) { + return NetworkImgLayer( + src: users.first.face, + width: size, + height: size, + type: .avatar, + ); + } else { + final decoration = BoxDecoration( + shape: .circle, + border: Border.all(color: colorScheme.surface), + ); + return SizedBox( + height: size, + width: offset * users.length + gap, + child: Stack( + clipBehavior: .none, + children: users.indexed + .map( + (e) => Positioned( + top: 0, + bottom: 0, + width: size, + left: e.$1 * offset, + child: DecoratedBox( + decoration: decoration, + child: Padding( + padding: const .all(.8), + child: NetworkImgLayer( + src: e.$2.face, + width: size - .8, + height: size - .8, + type: .avatar, + ), + ), + ), + ), + ) + .toList(), + ), + ); + } +} diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index 72ed369d6..fce93db1b 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart'; import 'package:PiliPlus/models/dynamics/article_content_model.dart'; import 'package:PiliPlus/models/model_avatar.dart'; +import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models_new/live/live_feed_index/watched_show.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; @@ -236,10 +237,12 @@ class ItemModulesModel { class ModuleFold { List? ids; String? statement; + List? users; ModuleFold.fromJson(Map json) { ids = (json['ids'] as List?)?.fromCast(); statement = json['statement']; + users = (json['users'] as List?)?.map((e) => Owner.fromJson(e)).toList(); } } diff --git a/lib/models_new/followee_votes/vote.dart b/lib/models_new/followee_votes/vote.dart index 87e983d7a..c33763428 100644 --- a/lib/models_new/followee_votes/vote.dart +++ b/lib/models_new/followee_votes/vote.dart @@ -1,20 +1,26 @@ -class FolloweeVote { - int uid; - String name; - String face; +import 'package:PiliPlus/models/model_owner.dart'; + +class FolloweeVote extends Owner { + String _name; + @override + String get name => _name; + String _face; + @override + String get face => _face; List votes; int ctime; FolloweeVote({ - required this.uid, - required this.name, - required this.face, + required super.mid, + required String name, + required String face, required this.votes, required this.ctime, - }); + }) : _name = name, + _face = face; factory FolloweeVote.fromJson(Map json) => FolloweeVote( - uid: json['uid'], + mid: json['uid'], name: json['name'], face: json['face'], votes: List.from(json['votes']), diff --git a/lib/pages/dynamics/widgets/dynamic_panel.dart b/lib/pages/dynamics/widgets/dynamic_panel.dart index 5b3bdb07a..b92d627d3 100644 --- a/lib/pages/dynamics/widgets/dynamic_panel.dart +++ b/lib/pages/dynamics/widgets/dynamic_panel.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/avatars.dart'; import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; @@ -93,39 +94,7 @@ class DynamicPanel extends StatelessWidget { height: 1, color: theme.dividerColor.withValues(alpha: 0.1), ), - InkWell( - onTap: onUnfold, - child: Container( - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text.rich( - textAlign: TextAlign.center, - style: TextStyle( - height: 1, - fontSize: 13, - color: theme.colorScheme.outline, - ), - strutStyle: const StrutStyle( - height: 1, - leading: 0, - fontSize: 13, - ), - TextSpan( - children: [ - TextSpan(text: moduleFold.statement ?? '展开'), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Icon( - size: 19, - Icons.keyboard_arrow_down, - color: theme.colorScheme.outline, - ), - ), - ], - ), - ), - ), - ), + _buildFoldItem(theme, moduleFold), ], ] else if (!isSave) const SizedBox(height: 12), @@ -216,4 +185,52 @@ class DynamicPanel extends StatelessWidget { bvid: bvid, ); } + + Widget _buildFoldItem(ThemeData theme, ModuleFold moduleFold) { + Widget child = Text.rich( + textAlign: TextAlign.center, + style: TextStyle( + height: 1, + fontSize: 13, + color: theme.colorScheme.outline, + ), + strutStyle: const StrutStyle( + height: 1, + leading: 0, + fontSize: 13, + ), + TextSpan( + children: [ + TextSpan(text: moduleFold.statement ?? '展开'), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Icon( + size: 19, + Icons.keyboard_arrow_down, + color: theme.colorScheme.outline, + ), + ), + ], + ), + ); + final users = moduleFold.users; + if (users != null && users.isNotEmpty) { + child = Row( + spacing: 5, + mainAxisAlignment: .center, + children: [ + avatars(colorScheme: theme.colorScheme, users: users), + child, + ], + ); + } + return InkWell( + onTap: onUnfold, + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 10), + child: child, + ), + ); + } } diff --git a/lib/pages/dynamics/widgets/vote.dart b/lib/pages/dynamics/widgets/vote.dart index d5bc61897..47b03ac05 100644 --- a/lib/pages/dynamics/widgets/vote.dart +++ b/lib/pages/dynamics/widgets/vote.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math'; +import 'package:PiliPlus/common/widgets/avatars.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/dialog/report.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -149,51 +150,6 @@ class _VotePanelState extends State { Obx(() { final list = followeeVote.value; if (list != null && list.isNotEmpty) { - Widget child; - const size = 22.0; - const gap = 6.0; - const offset = size - gap; - if (list.length == 1) { - child = NetworkImgLayer( - src: list.first.face, - width: size, - height: size, - ); - } else { - final decoration = BoxDecoration( - shape: .circle, - border: Border.all(color: theme.colorScheme.surface), - ); - child = SizedBox( - height: size, - width: offset * min(3, list.length) + gap, - child: Stack( - clipBehavior: .none, - children: list - .take(3) - .indexed - .map( - (e) => Positioned( - top: 0, - left: e.$1 * offset, - bottom: 0, - child: DecoratedBox( - decoration: decoration, - child: Padding( - padding: const .all(.8), - child: NetworkImgLayer( - src: e.$2.face, - width: size - .8, - height: size - .8, - ), - ), - ), - ), - ) - .toList(), - ), - ); - } return GestureDetector( behavior: .opaque, onTap: () { @@ -213,7 +169,7 @@ class _VotePanelState extends State { (e) => ListTile( dense: true, onTap: () => - Get.toNamed('/member?mid=${e.uid}'), + Get.toNamed('/member?mid=${e.mid}'), leading: NetworkImgLayer( src: e.face, width: 40, @@ -259,7 +215,10 @@ class _VotePanelState extends State { child: Row( mainAxisSize: .min, children: [ - child, + avatars( + colorScheme: theme.colorScheme, + users: list.take(3), + ), Icon( size: 18, color: theme.colorScheme.outline.withValues(alpha: .7), diff --git a/lib/pages/member/widget/user_info_card.dart b/lib/pages/member/widget/user_info_card.dart index 4fce6482d..9861c1d39 100644 --- a/lib/pages/member/widget/user_info_card.dart +++ b/lib/pages/member/widget/user_info_card.dart @@ -1,9 +1,8 @@ import 'package:PiliPlus/common/constants.dart'; -import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/common/widgets/avatars.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart'; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models/common/member/user_info_type.dart'; import 'package:PiliPlus/models_new/space/space/card.dart'; import 'package:PiliPlus/models_new/space/space/followings_followed_upper.dart'; @@ -601,50 +600,11 @@ class UserInfoCard extends StatelessWidget { var list = item.items!; final flag = list.length > 3; if (flag) list = list.sublist(0, 3); - final length = list.length; - const size = 22.0; - Widget avatar(String url) => NetworkImgLayer( - src: url, - width: size, - height: size, - type: ImageType.avatar, - ); - Widget avatars; - if (length == 1) { - avatars = avatar(list.first.face!); - } else { - const gap = 4.0; - const offset = size - gap; - final decoration = BoxDecoration( - shape: BoxShape.circle, - border: Border.all(color: colorScheme.surface), - ); - avatars = SizedBox( - width: length * size - (length - 1) * gap, - height: size + 1.6, - child: Stack( - clipBehavior: Clip.none, - children: List.generate( - length, - (index) => Positioned( - right: index * offset, - child: DecoratedBox( - decoration: decoration, - child: Padding( - padding: const EdgeInsets.all(.8), - child: avatar(list[length - 1 - index].face!), - ), - ), - ), - ), - ), - ); - } Widget child = Row( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(width: 20), - avatars, + avatars(colorScheme: colorScheme, users: list), const SizedBox(width: 4), Flexible( child: Text(