Files
tajniak81 d6956395c8 Rebrand CommGate to gsmnode and adopt new design system
Rename the whole project from CommGate to gsmnode and re-skin every
surface onto the new signal-green + ink design system (Space Grotesk /
IBM Plex Sans / JetBrains Mono, flat surfaces, two-arrow routing mark,
lowercase gsm+node wordmark).

- Web App + API panel: rewrite token layer, gn-* utilities, data-gsm-theme,
  new logo/favicon assets; both builds verified.
- Phone App (Flutter): green theme, new mark/wordmark widgets, adaptive
  launcher icons; rename Android applicationId/package to app.gsmnode.phone;
  bump AGP to 8.6.0 and google_fonts to 8.1.0; debug APK build verified.
- API Server (Go): panel routes, User-Agent, health service id, branding.
- Home Assistant Plugin: rename integration domain sms_gateway to gsmnode
  (folder, DOMAIN, services, entity ids, classes); py_compile verified.
- Update all READMEs; add .gitignore (excludes Design/, build artifacts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 08:53:14 +02:00

103 lines
3.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../theme.dart';
/// The gsmnode routing mark — two stacked arrows: the top (ink) routes right,
/// the bottom (green) routes left. Geometry matches assets/svg/mark-color.svg
/// (2.6px stroke on a 40px grid, rounded terminals).
///
/// When [color] is given the whole mark is drawn in that single color
/// (e.g. white on a filled tile); otherwise the top arrow takes the surface
/// ink color and the bottom arrow the signal green.
class GsmNodeMark extends StatelessWidget {
const GsmNodeMark({super.key, this.size = 32, this.color});
final double size;
final Color? color;
@override
Widget build(BuildContext context) {
final ink = color ?? Theme.of(context).colorScheme.onSurface;
final green = color ?? GsmColors.green500;
return CustomPaint(
size: Size.square(size),
painter: _MarkPainter(ink: ink, green: green),
);
}
}
class _MarkPainter extends CustomPainter {
_MarkPainter({required this.ink, required this.green});
final Color ink;
final Color green;
@override
void paint(Canvas canvas, Size size) {
final k = size.width / 40; // scale from the 40px design grid
Paint stroke(Color c) => Paint()
..color = c
..style = PaintingStyle.stroke
..strokeWidth = 2.6 * k
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round;
final inkPaint = stroke(ink);
final greenPaint = stroke(green);
// Top arrow — routes right (ink).
canvas.drawLine(Offset(7 * k, 15 * k), Offset(31 * k, 15 * k), inkPaint);
canvas.drawPath(
Path()
..moveTo(26 * k, 10 * k)
..lineTo(32 * k, 15 * k)
..lineTo(26 * k, 20 * k),
inkPaint,
);
// Bottom arrow — routes left (green).
canvas.drawLine(Offset(9 * k, 25 * k), Offset(33 * k, 25 * k), greenPaint);
canvas.drawPath(
Path()
..moveTo(14 * k, 20 * k)
..lineTo(8 * k, 25 * k)
..lineTo(14 * k, 30 * k),
greenPaint,
);
}
@override
bool shouldRepaint(_MarkPainter old) => old.ink != ink || old.green != green;
}
/// The gsmnode wordmark: lowercase `gsm` in ink + `node` in green, monospace.
/// On dark surfaces `node` lifts to the on-dark green. Pass [color] to render
/// the whole wordmark in a single color.
class GsmNodeWordmark extends StatelessWidget {
const GsmNodeWordmark({super.key, this.size = 24, this.color});
final double size;
final Color? color;
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final ink = color ?? Theme.of(context).colorScheme.onSurface;
final green =
color ?? (isDark ? GsmColors.greenOnDark : GsmColors.green500);
return Text.rich(
TextSpan(children: [
TextSpan(text: 'gsm', style: TextStyle(color: ink)),
TextSpan(text: 'node', style: TextStyle(color: green)),
]),
style: GoogleFonts.jetBrainsMono(
fontSize: size,
fontWeight: FontWeight.w600,
letterSpacing: -0.01 * size,
height: 1,
),
);
}
}