diff --git a/lib/pages/dynamics/widgets/vote.dart b/lib/pages/dynamics/widgets/vote.dart index 0d51ddf14..0955d0534 100644 --- a/lib/pages/dynamics/widgets/vote.dart +++ b/lib/pages/dynamics/widgets/vote.dart @@ -1,10 +1,14 @@ import 'dart:async'; +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'; import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/dynamics/vote_model.dart'; import 'package:PiliPlus/utils/date_utils.dart'; +import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart' hide ContextExtensionss; @@ -27,7 +31,8 @@ class _VotePanelState extends State { bool anonymity = false; late VoteInfo _voteInfo; - late final RxSet groupValue = (_voteInfo.myVotes?.toSet() ?? {}).obs; + late final RxList groupValue = + (_voteInfo.myVotes?.toList() ?? []).obs; late var _percentage = _cnt2Percentage(_voteInfo.options); late bool _enabled = groupValue.isEmpty && @@ -61,10 +66,30 @@ class _VotePanelState extends State { ], ), Flexible( - child: ListView.builder( - shrinkWrap: true, - itemCount: _voteInfo.options.length, - itemBuilder: (context, index) => _buildOptions(index), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: _voteInfo.type == 1 + ? GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: _voteInfo.options.length, + gridDelegate: SliverGridDelegateWithExtentAndRatio( + maxCrossAxisExtent: 100, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + mainAxisExtent: MediaQuery.textScalerOf(context).scale(50), + ), + itemBuilder: (context, index) => + _buildPicOptions(index, theme.colorScheme), + ) + : ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: _voteInfo.options.length, + itemBuilder: (context, index) => _buildOptions(index), + separatorBuilder: (context, index) => + const SizedBox(height: 10), + ), ), ), if (_enabled) ...[ @@ -76,7 +101,7 @@ class _VotePanelState extends State { onPressed: groupValue.isNotEmpty ? () async { final res = await widget.callback( - groupValue, + groupValue.toSet(), anonymity, ); if (res.isSuccess) { @@ -177,41 +202,140 @@ class _VotePanelState extends State { ], ); - Widget _buildOptions(int index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), + Widget _buildPicOptions(int index, ColorScheme colorScheme) { + return Card( + clipBehavior: Clip.hardEdge, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(6)), + ), child: Builder( builder: (context) { final opt = _voteInfo.options[index]; final selected = groupValue.contains(opt.optIdx); - return PercentageChip( - label: opt.optDesc!, - percentage: _showPercentage ? _percentage[index] : null, - selected: selected, - onSelected: !_enabled || (groupValue.length >= _maxCnt && !selected) + return InkWell( + onTap: !_enabled ? null - : (value) => _onSelected(context, value, opt.optIdx!), + : () => _onSelected(context, !selected, opt.optIdx!), + child: Column( + spacing: 5, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Stack( + clipBehavior: Clip.none, + children: [ + AspectRatio( + aspectRatio: 1, + child: LayoutBuilder( + builder: (context, constraints) => NetworkImgLayer( + src: opt.imgUrl, + width: constraints.maxWidth, + height: constraints.maxHeight, + radius: 0, + ), + ), + ), + if (_enabled) + Positioned( + right: 4, + top: 4, + width: 20, + height: 20, + child: DecoratedBox( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: selected + ? colorScheme.primaryContainer + : null, + border: selected + ? null + : Border.all( + color: colorScheme.primaryContainer, + ), + ), + child: selected + ? Icon( + size: 15, + Icons.check_rounded, + color: colorScheme.onPrimaryContainer, + ) + : null, + ), + ), + if (_showPercentage) ...[ + Positioned( + left: 0, + right: 0, + bottom: -1, + child: LinearProgressIndicator( + // ignore: deprecated_member_use + year2023: true, + value: _percentage[index], + ), + ), + PBadge( + right: 6, + bottom: 8, + type: PBadgeType.primary, + padding: const EdgeInsets.symmetric( + horizontal: 3, + vertical: 1, + ), + text: + '${(_percentage[index] * 100).toStringAsFixed(0)}%', + ), + ], + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: Text( + opt.optDesc!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 13), + ), + ), + ], + ), ); }, ), ); } - void _onSelected(BuildContext context, bool value, int optidx) { - bool isMax = groupValue.length >= _maxCnt; + Widget _buildOptions(int index) { + return Builder( + builder: (context) { + final opt = _voteInfo.options[index]; + final selected = groupValue.contains(opt.optIdx); + return PercentageChip( + label: opt.optDesc!, + percentage: _showPercentage ? _percentage[index] : null, + selected: selected, + onSelected: !_enabled + ? null + : (value) => _onSelected(context, value, opt.optIdx!), + ); + }, + ); + } + + void _onSelected(BuildContext itemCtx, bool value, int optidx) { + final bool isMax = groupValue.length >= _maxCnt; + if (isMax && value && !groupValue.contains(optidx)) { + groupValue + ..removeAt(0) + ..add(optidx); + setState(() {}); + return; + } + if (value) { groupValue.add(optidx); } else { groupValue.remove(optidx); } - if ((isMax && - _maxCnt != _voteInfo.options.length && - groupValue.length < _maxCnt) || - (groupValue.length >= _maxCnt && _maxCnt < _voteInfo.options.length)) { - setState(() {}); - } else { - (context as Element).markNeedsBuild(); - } + (itemCtx as Element).markNeedsBuild(); } static List _cnt2Percentage(List