mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-08-03 17:20:16 +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/common/image_preview_type.dart';
|
||||||
import 'package:PiliPlus/models/dynamics/vote_model.dart';
|
import 'package:PiliPlus/models/dynamics/vote_model.dart';
|
||||||
import 'package:PiliPlus/models_new/followee_votes/vote.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/accounts.dart';
|
||||||
import 'package:PiliPlus/utils/date_utils.dart';
|
import 'package:PiliPlus/utils/date_utils.dart';
|
||||||
import 'package:PiliPlus/utils/grid.dart';
|
import 'package:PiliPlus/utils/grid.dart';
|
||||||
@@ -84,7 +85,7 @@ class _VotePanelState extends State<VotePanel> {
|
|||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
padding: const .symmetric(vertical: 8),
|
||||||
child: _voteInfo.type == 1
|
child: _voteInfo.type == 1
|
||||||
? GridView.builder(
|
? GridView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@@ -105,7 +106,7 @@ class _VotePanelState extends State<VotePanel> {
|
|||||||
itemCount: _voteInfo.options.length,
|
itemCount: _voteInfo.options.length,
|
||||||
itemBuilder: (context, index) => _buildOptions(index),
|
itemBuilder: (context, index) => _buildOptions(index),
|
||||||
separatorBuilder: (context, index) =>
|
separatorBuilder: (context, index) =>
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -428,7 +429,7 @@ class _VotePanelState extends State<VotePanel> {
|
|||||||
selected: selected,
|
selected: selected,
|
||||||
onSelected: !_enabled
|
onSelected: !_enabled
|
||||||
? null
|
? null
|
||||||
: (value) => _onSelected(context, value, opt.optIdx!),
|
: () => _onSelected(context, !selected, opt.optIdx!),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -464,7 +465,7 @@ class PercentageChip extends StatelessWidget {
|
|||||||
final String label;
|
final String label;
|
||||||
final double? percentage;
|
final double? percentage;
|
||||||
final bool selected;
|
final bool selected;
|
||||||
final ValueChanged<bool>? onSelected;
|
final VoidCallback? onSelected;
|
||||||
|
|
||||||
const PercentageChip({
|
const PercentageChip({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -474,48 +475,41 @@ class PercentageChip extends StatelessWidget {
|
|||||||
this.percentage,
|
this.percentage,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static final EdgeInsets _padding = PlatformUtils.isMobile
|
||||||
|
? const .symmetric(horizontal: 12, vertical: 10)
|
||||||
|
: const .symmetric(horizontal: 12, vertical: 8);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = ColorScheme.of(context);
|
||||||
return ChoiceChip(
|
return Tooltip(
|
||||||
tooltip: label,
|
message: label,
|
||||||
labelPadding: EdgeInsets.zero,
|
child: Material(
|
||||||
padding: EdgeInsets.zero,
|
borderRadius: const .all(.circular(8)),
|
||||||
showCheckmark: false,
|
color: selected ? colorScheme.secondaryContainer : null,
|
||||||
clipBehavior: Clip.hardEdge,
|
child: InkWell(
|
||||||
label: Stack(
|
onTap: onSelected,
|
||||||
clipBehavior: Clip.none,
|
borderRadius: const .all(.circular(8)),
|
||||||
alignment: Alignment.center,
|
child: Container(
|
||||||
children: [
|
padding: _padding,
|
||||||
if (percentage != null)
|
decoration: VoteDecoration(
|
||||||
Positioned.fill(
|
borderRadius: const .all(.circular(8)),
|
||||||
left: 0,
|
border: .all(color: colorScheme.outlineVariant),
|
||||||
child: FractionallySizedBox(
|
percentage: percentage ?? 0,
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
widthFactor: percentage,
|
|
||||||
child: ColoredBox(
|
|
||||||
color: selected
|
color: selected
|
||||||
? colorScheme.inversePrimary
|
? colorScheme.inversePrimary
|
||||||
: colorScheme.outlineVariant,
|
: colorScheme.outlineVariant,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
spacing: 4,
|
spacing: 4,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(label, maxLines: 1, overflow: .ellipsis),
|
||||||
label,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (selected)
|
if (selected)
|
||||||
Icon(
|
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