Files
pyramid/lib/widgets/pyramid_logo.dart
T
Bernd Steckmeister 25ed765a03 wip: Sicherungs-Commit aller Änderungen seit April + Arbeitsstruktur (CLAUDE.md, ROADMAP.md, PROGRESS.md, Autopilot)
6 Wochen uncommittete Arbeit (Voice-Channels, LiveKit-Manager, Settings-Modal u.v.m.)
als ein WIP-Commit gesichert, damit nichts verloren geht und der Pi den aktuellen
Stand klonen kann. Thematische Aufarbeitung: siehe ROADMAP M0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPrAGBxBT6GfPXzeWQ4AXb
2026-07-03 05:47:18 +02:00

188 lines
5.0 KiB
Dart

import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
// 3D Pyramid logo — two faces + base outline + spine
class PyramidLogo extends StatelessWidget {
final double size;
final Color color;
const PyramidLogo({super.key, this.size = 36, this.color = Colors.white});
@override
Widget build(BuildContext context) =>
CustomPaint(size: Size(size, size), painter: _PyramidLogoPainter(color: color));
}
class _PyramidLogoPainter extends CustomPainter {
final Color color;
const _PyramidLogoPainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final w = size.width;
final h = size.height;
final sx = w / 48;
final sy = h / 48;
Offset p(double x, double y) => Offset(x * sx, y * sy);
final apex = p(24, 6);
final baseL = p(6, 40);
final baseR = p(42, 40);
final baseM = p(24, 34);
// Left face — full opacity → 55%
final leftPath = Path()..moveTo(apex.dx, apex.dy)..lineTo(baseL.dx, baseL.dy)..lineTo(baseM.dx, baseM.dy)..close();
canvas.drawPath(leftPath, Paint()..shader = ui.Gradient.linear(
Offset(0, apex.dy), Offset(0, baseL.dy),
[color.withAlpha(255), color.withAlpha(140)],
));
// Right face — 85% → 25%
final rightPath = Path()..moveTo(apex.dx, apex.dy)..lineTo(baseR.dx, baseR.dy)..lineTo(baseM.dx, baseM.dy)..close();
canvas.drawPath(rightPath, Paint()..shader = ui.Gradient.linear(
Offset(apex.dx, apex.dy), Offset(baseR.dx, baseR.dy),
[color.withAlpha(217), color.withAlpha(64)],
));
// Base outline
canvas.drawPath(
Path()..moveTo(baseL.dx, baseL.dy)..lineTo(baseM.dx, baseM.dy)..lineTo(baseR.dx, baseR.dy),
Paint()..color = color.withAlpha(102)..style = PaintingStyle.stroke
..strokeWidth = math.max(sx, 0.7)..strokeJoin = StrokeJoin.round,
);
// Spine
canvas.drawLine(apex, baseM, Paint()..color = color.withAlpha(230)
..strokeWidth = math.max(sx * 0.8, 0.6));
}
@override
bool shouldRepaint(_PyramidLogoPainter old) => old.color != color;
}
// Pyramid mark — stylised stacked triangles
class PyramidMark extends StatelessWidget {
final double size;
final Color? color;
const PyramidMark({super.key, this.size = 30, this.color});
@override
Widget build(BuildContext context) {
final c = color ?? Colors.white;
return SizedBox(
width: size,
height: size,
child: CustomPaint(painter: _PyramidPainter(c)),
);
}
}
class _PyramidPainter extends CustomPainter {
final Color color;
_PyramidPainter(this.color);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color..style = PaintingStyle.fill;
final w = size.width;
final h = size.height;
// bottom big triangle
final big = Path()
..moveTo(w * 0.5, h * 0.1)
..lineTo(w * 0.92, h * 0.88)
..lineTo(w * 0.08, h * 0.88)
..close();
canvas.drawPath(big, paint);
// inner cutout (dark) to create layered look
final cut = Paint()
..color = Colors.black.withAlpha(80)
..style = PaintingStyle.fill;
final inner = Path()
..moveTo(w * 0.5, h * 0.28)
..lineTo(w * 0.74, h * 0.72)
..lineTo(w * 0.26, h * 0.72)
..close();
canvas.drawPath(inner, cut);
}
@override
bool shouldRepaint(_PyramidPainter old) => old.color != color;
}
// Pyramid space glyph (letter + triangle overlay)
class PyramidGlyph extends StatelessWidget {
final double size;
final Color color;
final String label;
const PyramidGlyph({
super.key,
this.size = 30,
required this.color,
required this.label,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: size,
height: size,
child: Stack(
alignment: Alignment.center,
children: [
CustomPaint(
size: Size(size, size),
painter: _PyramidPainter(color),
),
Text(
label.isNotEmpty ? label[0].toUpperCase() : '?',
style: TextStyle(
color: Colors.white,
fontSize: size * 0.38,
fontWeight: FontWeight.bold,
height: 1,
),
),
],
),
);
}
}
// Presence indicator dot
class PresenceDot extends StatelessWidget {
final String status; // online, away, busy, offline
final double size;
final Color borderColor;
const PresenceDot({
super.key,
required this.status,
this.size = 10,
this.borderColor = const Color(0xFF111114),
});
Color get _color => switch (status) {
'online' => const Color(0xFF4ADE80),
'away' => const Color(0xFFFACC15),
'busy' => const Color(0xFFF87171),
_ => const Color(0xFF6F6F7D),
};
@override
Widget build(BuildContext context) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
color: _color,
shape: BoxShape.circle,
border: Border.all(color: borderColor, width: 2),
),
);
}
}