mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-08-02 00:36:20 +08:00
@@ -10,6 +10,7 @@ import 'package:PiliPlus/models/common/badge_type.dart';
|
||||
import 'package:PiliPlus/models/common/image_preview_type.dart';
|
||||
import 'package:PiliPlus/models/dynamics/vote_model.dart';
|
||||
import 'package:PiliPlus/models_new/followee_votes/vote.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/widgets/vote_decoration.dart';
|
||||
import 'package:PiliPlus/utils/accounts.dart';
|
||||
import 'package:PiliPlus/utils/date_utils.dart';
|
||||
import 'package:PiliPlus/utils/grid.dart';
|
||||
@@ -84,7 +85,7 @@ class _VotePanelState extends State<VotePanel> {
|
||||
),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
padding: const .symmetric(vertical: 8),
|
||||
child: _voteInfo.type == 1
|
||||
? GridView.builder(
|
||||
shrinkWrap: true,
|
||||
@@ -105,7 +106,7 @@ class _VotePanelState extends State<VotePanel> {
|
||||
itemCount: _voteInfo.options.length,
|
||||
itemBuilder: (context, index) => _buildOptions(index),
|
||||
separatorBuilder: (context, index) =>
|
||||
const SizedBox(height: 6),
|
||||
const SizedBox(height: 8),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -428,7 +429,7 @@ class _VotePanelState extends State<VotePanel> {
|
||||
selected: selected,
|
||||
onSelected: !_enabled
|
||||
? null
|
||||
: (value) => _onSelected(context, value, opt.optIdx!),
|
||||
: () => _onSelected(context, !selected, opt.optIdx!),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -464,7 +465,7 @@ class PercentageChip extends StatelessWidget {
|
||||
final String label;
|
||||
final double? percentage;
|
||||
final bool selected;
|
||||
final ValueChanged<bool>? onSelected;
|
||||
final VoidCallback? onSelected;
|
||||
|
||||
const PercentageChip({
|
||||
super.key,
|
||||
@@ -474,48 +475,41 @@ class PercentageChip extends StatelessWidget {
|
||||
this.percentage,
|
||||
});
|
||||
|
||||
static final EdgeInsets _padding = PlatformUtils.isMobile
|
||||
? const .symmetric(horizontal: 12, vertical: 10)
|
||||
: const .symmetric(horizontal: 12, vertical: 8);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return ChoiceChip(
|
||||
tooltip: label,
|
||||
labelPadding: EdgeInsets.zero,
|
||||
padding: EdgeInsets.zero,
|
||||
showCheckmark: false,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
label: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
if (percentage != null)
|
||||
Positioned.fill(
|
||||
left: 0,
|
||||
child: FractionallySizedBox(
|
||||
alignment: Alignment.centerLeft,
|
||||
widthFactor: percentage,
|
||||
child: ColoredBox(
|
||||
color: selected
|
||||
? colorScheme.inversePrimary
|
||||
: colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
final colorScheme = ColorScheme.of(context);
|
||||
return Tooltip(
|
||||
message: label,
|
||||
child: Material(
|
||||
borderRadius: const .all(.circular(8)),
|
||||
color: selected ? colorScheme.secondaryContainer : null,
|
||||
child: InkWell(
|
||||
onTap: onSelected,
|
||||
borderRadius: const .all(.circular(8)),
|
||||
child: Container(
|
||||
padding: _padding,
|
||||
decoration: VoteDecoration(
|
||||
borderRadius: const .all(.circular(8)),
|
||||
border: .all(color: colorScheme.outlineVariant),
|
||||
percentage: percentage ?? 0,
|
||||
color: selected
|
||||
? colorScheme.inversePrimary
|
||||
: colorScheme.outlineVariant,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
spacing: 4,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
child: Text(label, maxLines: 1, overflow: .ellipsis),
|
||||
),
|
||||
if (selected)
|
||||
Icon(
|
||||
@@ -531,10 +525,8 @@ class PercentageChip extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
selected: selected,
|
||||
onSelected: onSelected,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
177
lib/pages/dynamics/widgets/vote_decoration.dart
Normal file
177
lib/pages/dynamics/widgets/vote_decoration.dart
Normal file
@@ -0,0 +1,177 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class VoteDecoration extends Decoration {
|
||||
const VoteDecoration({
|
||||
required this.color,
|
||||
required this.percentage,
|
||||
required this.border,
|
||||
required this.borderRadius,
|
||||
});
|
||||
|
||||
final Color color;
|
||||
final double percentage;
|
||||
final BoxBorder border;
|
||||
final BorderRadius borderRadius;
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get padding => border.dimensions;
|
||||
|
||||
@override
|
||||
Path getClipPath(Rect rect, TextDirection textDirection) {
|
||||
return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is VoteDecoration &&
|
||||
other.color == color &&
|
||||
other.border == border &&
|
||||
other.borderRadius == borderRadius &&
|
||||
other.percentage == percentage;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(color, border, borderRadius, percentage);
|
||||
|
||||
@override
|
||||
bool hitTest(Size size, Offset position, {TextDirection? textDirection}) {
|
||||
assert((Offset.zero & size).contains(position));
|
||||
final Offset center = size.center(Offset.zero);
|
||||
final double distance = (position - center).distance;
|
||||
return distance <= math.min(size.width, size.height) / 2.0;
|
||||
}
|
||||
|
||||
@override
|
||||
BoxPainter createBoxPainter([VoidCallback? onChanged]) {
|
||||
assert(onChanged != null);
|
||||
return _VoteDecorationPainter(this, onChanged);
|
||||
}
|
||||
}
|
||||
|
||||
class _VoteDecorationPainter extends BoxPainter {
|
||||
_VoteDecorationPainter(this._decoration, super.onChanged);
|
||||
|
||||
final VoteDecoration _decoration;
|
||||
|
||||
Paint? _cachedBackgroundPaint;
|
||||
Rect? _rectForCachedBackgroundPaint;
|
||||
Paint _getBackgroundPaint(Rect rect, TextDirection? textDirection) {
|
||||
assert(
|
||||
_rectForCachedBackgroundPaint == null,
|
||||
);
|
||||
|
||||
if (_cachedBackgroundPaint == null) {
|
||||
final paint = Paint()..color = _decoration.color;
|
||||
_cachedBackgroundPaint = paint;
|
||||
}
|
||||
|
||||
return _cachedBackgroundPaint!;
|
||||
}
|
||||
|
||||
void _paintBox(
|
||||
Canvas canvas,
|
||||
Rect rect,
|
||||
Paint paint,
|
||||
TextDirection? textDirection,
|
||||
) {
|
||||
if (_decoration.percentage == 1.0) {
|
||||
canvas.drawRRect(
|
||||
_decoration.borderRadius.resolve(textDirection).toRRect(rect),
|
||||
paint,
|
||||
);
|
||||
} else {
|
||||
final borderRadius = BorderRadius.horizontal(
|
||||
left: _decoration.borderRadius.topLeft,
|
||||
);
|
||||
canvas.drawRRect(
|
||||
borderRadius.resolve(textDirection).toRRect(rect),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _paintBackgroundColor(
|
||||
Canvas canvas,
|
||||
Rect rect,
|
||||
TextDirection? textDirection,
|
||||
) {
|
||||
final Rect adjustedRect = _adjustedRectOnOutlinedBorder(
|
||||
rect,
|
||||
textDirection,
|
||||
);
|
||||
_paintBox(
|
||||
canvas,
|
||||
adjustedRect,
|
||||
_getBackgroundPaint(rect, textDirection),
|
||||
textDirection,
|
||||
);
|
||||
}
|
||||
|
||||
double _calculateAdjustedSide(BorderSide side) {
|
||||
if (side.color.alpha == 255 && side.style == .solid) {
|
||||
return side.strokeInset;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Rect _adjustedRectOnOutlinedBorder(Rect rect, TextDirection? textDirection) {
|
||||
final border = _decoration.border as Border;
|
||||
|
||||
final EdgeInsets insets =
|
||||
EdgeInsets.fromLTRB(
|
||||
_calculateAdjustedSide(border.left),
|
||||
_calculateAdjustedSide(border.top),
|
||||
_calculateAdjustedSide(border.right),
|
||||
_calculateAdjustedSide(border.bottom),
|
||||
) /
|
||||
2;
|
||||
|
||||
return Rect.fromLTRB(
|
||||
rect.left + insets.left,
|
||||
rect.top + insets.top,
|
||||
rect.right - insets.right,
|
||||
rect.bottom - insets.bottom,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
|
||||
assert(configuration.size != null);
|
||||
final Rect rect = offset & configuration.size!;
|
||||
final TextDirection? textDirection = configuration.textDirection;
|
||||
if (_decoration.percentage > 0) {
|
||||
final Rect bgRect;
|
||||
if (_decoration.percentage == 1.0) {
|
||||
bgRect = rect;
|
||||
} else {
|
||||
bgRect =
|
||||
offset &
|
||||
Size(
|
||||
configuration.size!.width * (_decoration.percentage),
|
||||
configuration.size!.height,
|
||||
);
|
||||
}
|
||||
_paintBackgroundColor(canvas, bgRect, textDirection);
|
||||
}
|
||||
_decoration.border.paint(
|
||||
canvas,
|
||||
rect,
|
||||
shape: .rectangle,
|
||||
borderRadius: _decoration.borderRadius.resolve(textDirection),
|
||||
textDirection: configuration.textDirection,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VotePainter for $_decoration';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user