Show ontology snowflake in admin reviews
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m55s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m55s
This commit is contained in:
@@ -1025,7 +1025,7 @@ class _AdminVoiceExperienceRow extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
_AdminOntologyTags(selectedTags: selectedTags),
|
||||
_AdminOntologySnowflake(selectedTags: selectedTags),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -1033,75 +1033,219 @@ class _AdminVoiceExperienceRow extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _AdminOntologyTags extends StatelessWidget {
|
||||
const _AdminOntologyTags({required this.selectedTags});
|
||||
class _AdminOntologySnowflake extends StatelessWidget {
|
||||
const _AdminOntologySnowflake({required this.selectedTags});
|
||||
|
||||
final Set<String> selectedTags;
|
||||
|
||||
static const _tags = [
|
||||
_AdminOntologyTag('energy:calm', 'спокойное'),
|
||||
_AdminOntologyTag('energy:dynamic', 'живое'),
|
||||
_AdminOntologyTag('privacy:intimate', 'камерное'),
|
||||
_AdminOntologyTag('privacy:open', 'открытое'),
|
||||
_AdminOntologyTag('sociality:solo', 'для себя'),
|
||||
_AdminOntologyTag('sociality:group', 'для компании'),
|
||||
_AdminOntologyTag('function:reset', 'выдохнуть'),
|
||||
_AdminOntologyTag('function:impress', 'впечатлить'),
|
||||
_AdminOntologyTag('function:transit', 'транзитное'),
|
||||
_AdminOntologyTag('aesthetic:clean', 'чистое'),
|
||||
_AdminOntologyTag('aesthetic:expressive', 'выразительное'),
|
||||
static const _axes = [
|
||||
_AdminOntologyAxis(
|
||||
id: 'energy',
|
||||
label: 'энергия',
|
||||
angle: -math.pi / 2,
|
||||
leaves: [
|
||||
_AdminOntologyLeaf('calm', 'спокойное', -0.22),
|
||||
_AdminOntologyLeaf('dynamic', 'живое', 0.22),
|
||||
],
|
||||
),
|
||||
_AdminOntologyAxis(
|
||||
id: 'privacy',
|
||||
label: 'приватность',
|
||||
angle: -math.pi / 2 + math.pi * 2 / 5,
|
||||
leaves: [
|
||||
_AdminOntologyLeaf('intimate', 'камерное', -0.2),
|
||||
_AdminOntologyLeaf('open', 'открытое', 0.2),
|
||||
],
|
||||
),
|
||||
_AdminOntologyAxis(
|
||||
id: 'function',
|
||||
label: 'сценарий',
|
||||
angle: -math.pi / 2 + math.pi * 4 / 5,
|
||||
leaves: [
|
||||
_AdminOntologyLeaf('reset', 'выдохнуть', -0.25),
|
||||
_AdminOntologyLeaf('impress', 'впечатлить', 0),
|
||||
_AdminOntologyLeaf('transit', 'транзитное', 0.25),
|
||||
],
|
||||
),
|
||||
_AdminOntologyAxis(
|
||||
id: 'aesthetic',
|
||||
label: 'образ',
|
||||
angle: -math.pi / 2 + math.pi * 6 / 5,
|
||||
leaves: [
|
||||
_AdminOntologyLeaf('clean', 'чистое', -0.2),
|
||||
_AdminOntologyLeaf('expressive', 'выразительное', 0.2),
|
||||
],
|
||||
),
|
||||
_AdminOntologyAxis(
|
||||
id: 'sociality',
|
||||
label: 'социальность',
|
||||
angle: -math.pi / 2 + math.pi * 8 / 5,
|
||||
leaves: [
|
||||
_AdminOntologyLeaf('solo', 'для себя', -0.2),
|
||||
_AdminOntologyLeaf('group', 'для компании', 0.2),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: [
|
||||
for (final tag in _tags)
|
||||
_AdminOntologyChip(
|
||||
label: tag.label,
|
||||
selected: selectedTags.contains(tag.id),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AdminOntologyChip extends StatelessWidget {
|
||||
const _AdminOntologyChip({required this.label, required this.selected});
|
||||
|
||||
final String label;
|
||||
final bool selected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? const Color(0xFFE11D48) : const Color(0xFFF2EEE8),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 5),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: selected ? Colors.white : const Color(0xFF746A60),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w800,
|
||||
height: 1,
|
||||
),
|
||||
return SizedBox(
|
||||
height: 300,
|
||||
width: double.infinity,
|
||||
child: CustomPaint(
|
||||
painter: _AdminOntologySnowflakePainter(
|
||||
axes: _axes,
|
||||
selectedTags: selectedTags,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AdminOntologyTag {
|
||||
const _AdminOntologyTag(this.id, this.label);
|
||||
class _AdminOntologySnowflakePainter extends CustomPainter {
|
||||
const _AdminOntologySnowflakePainter({
|
||||
required this.axes,
|
||||
required this.selectedTags,
|
||||
});
|
||||
|
||||
final List<_AdminOntologyAxis> axes;
|
||||
final Set<String> selectedTags;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final center = Offset(size.width / 2, size.height / 2);
|
||||
final radius = math.min(size.width, size.height);
|
||||
final axisRadius = radius * 0.25;
|
||||
final leafRadius = radius * 0.43;
|
||||
|
||||
final baseLine = Paint()
|
||||
..color = const Color(0xFFE6DDD2)
|
||||
..strokeWidth = 1.3
|
||||
..style = PaintingStyle.stroke;
|
||||
final selectedLine = Paint()
|
||||
..color = const Color(0xFFE11D48)
|
||||
..strokeWidth = 2.2
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke;
|
||||
final node = Paint()..color = const Color(0xFFDED3C7);
|
||||
final selectedNode = Paint()..color = const Color(0xFFE11D48);
|
||||
final centerNode = Paint()..color = const Color(0xFF241B18);
|
||||
|
||||
canvas.drawCircle(center, 5, centerNode);
|
||||
_drawLabel(canvas, size, center + const Offset(0, 14), 'место', true);
|
||||
|
||||
for (final axis in axes) {
|
||||
final axisOffset = Offset(math.cos(axis.angle), math.sin(axis.angle));
|
||||
final axisPoint = center + axisOffset * axisRadius;
|
||||
final hasSelectedLeaf = axis.leaves.any(
|
||||
(leaf) => selectedTags.contains('${axis.id}:${leaf.id}'),
|
||||
);
|
||||
|
||||
canvas.drawLine(
|
||||
center,
|
||||
axisPoint,
|
||||
hasSelectedLeaf ? selectedLine : baseLine,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
axisPoint,
|
||||
hasSelectedLeaf ? 5.5 : 4.5,
|
||||
hasSelectedLeaf ? selectedNode : node,
|
||||
);
|
||||
_drawLabel(
|
||||
canvas,
|
||||
size,
|
||||
axisPoint + axisOffset * 18,
|
||||
axis.label,
|
||||
hasSelectedLeaf,
|
||||
fontSize: 11,
|
||||
);
|
||||
|
||||
for (final leaf in axis.leaves) {
|
||||
final leafAngle = axis.angle + leaf.angleOffset;
|
||||
final leafOffset = Offset(math.cos(leafAngle), math.sin(leafAngle));
|
||||
final leafPoint = center + leafOffset * leafRadius;
|
||||
final tag = '${axis.id}:${leaf.id}';
|
||||
final selected = selectedTags.contains(tag);
|
||||
|
||||
canvas.drawLine(
|
||||
axisPoint,
|
||||
leafPoint,
|
||||
selected ? selectedLine : baseLine,
|
||||
);
|
||||
canvas.drawCircle(
|
||||
leafPoint,
|
||||
selected ? 8 : 5.5,
|
||||
selected ? selectedNode : node,
|
||||
);
|
||||
_drawLabel(
|
||||
canvas,
|
||||
size,
|
||||
leafPoint + leafOffset * 20,
|
||||
leaf.label,
|
||||
selected,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _drawLabel(
|
||||
Canvas canvas,
|
||||
Size size,
|
||||
Offset anchor,
|
||||
String label,
|
||||
bool selected, {
|
||||
double fontSize = 12,
|
||||
}) {
|
||||
final painter = TextPainter(
|
||||
text: TextSpan(
|
||||
text: label,
|
||||
style: TextStyle(
|
||||
color: selected ? const Color(0xFFE11D48) : const Color(0xFF746A60),
|
||||
fontSize: fontSize,
|
||||
fontWeight: selected ? FontWeight.w900 : FontWeight.w700,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
maxLines: 1,
|
||||
)..layout(maxWidth: 86);
|
||||
final dx = (anchor.dx - painter.width / 2).clamp(
|
||||
0.0,
|
||||
size.width - painter.width,
|
||||
);
|
||||
final dy = (anchor.dy - painter.height / 2).clamp(
|
||||
0.0,
|
||||
size.height - painter.height,
|
||||
);
|
||||
painter.paint(canvas, Offset(dx, dy));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _AdminOntologySnowflakePainter oldDelegate) {
|
||||
return oldDelegate.selectedTags != selectedTags;
|
||||
}
|
||||
}
|
||||
|
||||
class _AdminOntologyAxis {
|
||||
const _AdminOntologyAxis({
|
||||
required this.id,
|
||||
required this.label,
|
||||
required this.angle,
|
||||
required this.leaves,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String label;
|
||||
final double angle;
|
||||
final List<_AdminOntologyLeaf> leaves;
|
||||
}
|
||||
|
||||
class _AdminOntologyLeaf {
|
||||
const _AdminOntologyLeaf(this.id, this.label, this.angleOffset);
|
||||
|
||||
final String id;
|
||||
final String label;
|
||||
final double angleOffset;
|
||||
}
|
||||
|
||||
Set<String> _selectedAdminTags(Map<String, dynamic>? analysis) {
|
||||
|
||||
Reference in New Issue
Block a user