修复白屏BUG、UI 边界溢出 BUG

This commit is contained in:
2026-08-09 01:35:48 +08:00
parent 4a357024bc
commit 4385c6941e
24 changed files with 485 additions and 206 deletions
@@ -98,7 +98,9 @@ class _PointBookCreatePageState extends ConsumerState<PointBookCreatePage> {
Future<void> _start() async {
setState(() => _creating = true);
final draft = await ref.read(pointRecordsProvider.notifier).createDraft(
final draft = await ref
.read(pointRecordsProvider.notifier)
.createDraft(
bowType: _bowType,
targetFaceType: _targetFace,
distanceMeters: _distance,
@@ -187,11 +189,16 @@ class _NumberPicker extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontSize: 13)),
Flexible(child: Text(label, style: const TextStyle(fontSize: 13))),
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove_circle_outline),
iconSize: 20,
visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () {
final i = options.indexOf(value);
if (i > 0) onChanged(options[i - 1]);
@@ -206,6 +213,10 @@ class _NumberPicker extends StatelessWidget {
),
IconButton(
icon: const Icon(Icons.add_circle_outline),
iconSize: 20,
visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () {
final i = options.indexOf(value);
if (i < options.length - 1) onChanged(options[i + 1]);
@@ -6,7 +6,7 @@ import 'package:arcx/features/config/application/app_config_controller.dart';
import 'package:arcx/data/local/models/models.dart';
import 'package:arcx/features/scoring/application/score_math.dart';
import 'package:arcx/features/scoring/application/scoring_service.dart';
import 'package:arcx/features/scoring/presentation/widgets/target_face_painter.dart';
import 'package:arcx/features/scoring/presentation/widgets/hit_point_painter.dart';
class PointBookEditPage extends ConsumerStatefulWidget {
const PointBookEditPage({
@@ -70,6 +70,7 @@ class _PointBookEditPageState extends ConsumerState<PointBookEditPage> {
final isComplete = placed >= totalArrows;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_ProgressHeader(
record: record,
@@ -82,52 +83,61 @@ class _PointBookEditPageState extends ConsumerState<PointBookEditPage> {
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: LayoutBuilder(
builder: (context, constraints) {
final size = constraints.maxWidth;
return GestureDetector(
onTapUp: widget.readOnly || isComplete || _saving
? null
: (details) => _onTap(details, size, record),
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: size,
height: size,
child: CustomPaint(
painter: TargetFacePainter(
hitPoints: _hitPoints,
highlightIndex: _hitPoints.isEmpty
? null
: _hitPoints.length - 1,
child: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: LayoutBuilder(
builder: (context, constraints) {
final size = constraints.maxWidth;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTapUp: widget.readOnly || isComplete || _saving
? null
: (details) => _onTap(details, size, record),
child: Stack(
alignment: Alignment.center,
children: [
Image.asset(
'assets/images/target_face.png',
width: size,
height: size,
fit: BoxFit.fill,
),
),
CustomPaint(
size: Size(size, size),
painter: HitPointPainter(
hitPoints: _hitPoints,
highlightIndex: _hitPoints.isEmpty
? null
: _hitPoints.length - 1,
),
),
if (widget.readOnly && _hitPoints.isEmpty)
Text(
'无落点数据',
style: TextStyle(color: AppTheme.textSecondary),
),
if (!widget.readOnly && isComplete)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'已射完,点击右上角保存',
style: TextStyle(color: Colors.white),
),
),
],
),
if (widget.readOnly && _hitPoints.isEmpty)
Text(
'无落点数据',
style: TextStyle(color: AppTheme.textSecondary),
),
if (!widget.readOnly && isComplete)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'已射完,点击右上角保存',
style: TextStyle(color: Colors.white),
),
),
],
),
);
},
);
},
),
),
),
),
),
@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import '../../../../data/local/models/models.dart';
/// Paints hit points on the target face image.
/// Assumes the underlying image is a square with normalized coordinates
/// where (0, 0) is the center and radius 1.0 is the outer edge.
class HitPointPainter extends CustomPainter {
HitPointPainter({
this.hitPoints = const <HitPoint>[],
this.highlightIndex,
this.hitColor = const Color(0xFF1A1A1A),
});
final List<HitPoint> hitPoints;
final int? highlightIndex;
final Color hitColor;
@override
void paint(Canvas canvas, Size size) {
if (hitPoints.isEmpty) return;
final center = Offset(size.width / 2, size.height / 2);
final radius = size.shortestSide / 2;
for (var i = 0; i < hitPoints.length; i++) {
final hp = hitPoints[i];
final point = Offset(
center.dx + hp.normalizedX * radius,
center.dy + hp.normalizedY * radius,
);
final isHighlight = i == highlightIndex;
final dotRadius = isHighlight ? radius * 0.045 : radius * 0.03;
// White outer ring for visibility
canvas.drawCircle(
point,
dotRadius + 2,
Paint()..color = Colors.white,
);
// Inner colored dot
canvas.drawCircle(
point,
dotRadius,
Paint()..color = isHighlight ? const Color(0xFF16A34A) : hitColor,
);
}
}
@override
bool shouldRepaint(covariant HitPointPainter oldDelegate) {
return oldDelegate.hitPoints.length != hitPoints.length ||
oldDelegate.highlightIndex != highlightIndex ||
oldDelegate.hitColor != hitColor;
}
}
@@ -1,95 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../data/local/models/models.dart';
/// Paints a standard 10-ring archery target face and optionally overlays
/// recorded [HitPoint]s. Coordinates use the normalized convention where
/// (0, 0) is the center and radius 1.0 is the outer scoring edge.
class TargetFacePainter extends CustomPainter {
TargetFacePainter({
this.hitPoints = const <HitPoint>[],
this.highlightIndex,
this.showAllHits = true,
this.hitColor = const Color(0xFF1A1A1A),
this.ringStrokeWidth = 1.0,
});
final List<HitPoint> hitPoints;
final int? highlightIndex;
final bool showAllHits;
final Color hitColor;
final double ringStrokeWidth;
// Ring colors from center outward: 10,9 gold | 8,7 red | 6,5 blue | 4,3 black | 2,1 white
static const _ringColors = <Color>[
Color(0xFFFFD23F), // 10
Color(0xFFFFD23F), // 9
Color(0xFFE5484D), // 8
Color(0xFFE5484D), // 7
Color(0xFF3B82C4), // 6
Color(0xFF3B82C4), // 5
Color(0xFF2B2B2B), // 4
Color(0xFF2B2B2B), // 3
Color(0xFFF5F5F5), // 2
Color(0xFFF5F5F5), // 1
];
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = size.shortestSide / 2;
// Draw rings from outermost (1) inward to (10).
for (var i = _ringColors.length - 1; i >= 0; i--) {
final ringRadius = radius * (i + 1) / _ringColors.length;
final paint = Paint()..color = _ringColors[i];
canvas.drawCircle(center, ringRadius, paint);
}
// X ring (center dot).
canvas.drawCircle(
center,
radius * 0.05,
Paint()..color = const Color(0xFF8A6D00),
);
// Ring separator lines.
final stroke = Paint()
..color = const Color(0x55000000)
..style = PaintingStyle.stroke
..strokeWidth = ringStrokeWidth;
for (var i = 1; i <= _ringColors.length; i++) {
canvas.drawCircle(center, radius * i / _ringColors.length, stroke);
}
if (!showAllHits) return;
for (var i = 0; i < hitPoints.length; i++) {
final hp = hitPoints[i];
final point = Offset(
center.dx + hp.normalizedX * radius,
center.dy + hp.normalizedY * radius,
);
final isHighlight = i == highlightIndex;
final dotRadius = isHighlight ? radius * 0.045 : radius * 0.03;
canvas.drawCircle(
point,
dotRadius + 2,
Paint()..color = Colors.white,
);
canvas.drawCircle(
point,
dotRadius,
Paint()..color = isHighlight ? const Color(0xFF16A34A) : hitColor,
);
}
}
@override
bool shouldRepaint(covariant TargetFacePainter oldDelegate) {
return oldDelegate.hitPoints.length != hitPoints.length ||
oldDelegate.highlightIndex != highlightIndex ||
oldDelegate.hitColor != hitColor;
}
}