MotorSignal-App/lib/features/social/clubs/widgets/club_monogram.dart

115 lines
3.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import '../../../../core/theme/app_typography.dart';
import '../../../../core/theme/theme_extensions.dart';
/// Reusable widget rendering a deterministic 1–2 character monogram badge
/// derived from a Club's name. Follows automotive cockpit design guidelines.
class ClubMonogram extends StatelessWidget {
final String name;
final double size;
final double? fontSize;
final Color? backgroundColor;
final Color? textColor;
const ClubMonogram({
super.key,
required this.name,
this.size = 44.0,
this.fontSize,
this.backgroundColor,
this.textColor,
});
/// Computes a deterministic 1–2 character monogram from a raw club name.
/// Unicode-safe using Characters to handle grapheme clusters, emojis,
/// leading punctuation, non-Latin scripts, and single characters.
static String getMonogram(String rawName) {
final trimmed = rawName.trim();
if (trimmed.isEmpty) return 'C';
final words = trimmed
.split(RegExp(r'\s+'))
.where((w) => w.isNotEmpty)
.toList();
if (words.isEmpty) return 'C';
String cleanWord(String word) {
final chars = word.characters.toList();
int start = 0;
while (start < chars.length) {
final ch = chars[start];
if (_isLeadingPunctuation(ch)) {
start++;
} else {
break;
}
}
if (start >= chars.length) {
return word;
}
return chars.sublist(start).join();
}
if (words.length == 1) {
final cleaned = cleanWord(words.first);
final chars = cleaned.characters.take(2).toList();
if (chars.isEmpty) return 'C';
return chars.join().toUpperCase();
} else {
final cleanedWords = words.map(cleanWord).toList();
final firstGrapheme = cleanedWords[0].characters.isNotEmpty
? cleanedWords[0].characters.first
: '';
final secondGrapheme = cleanedWords[1].characters.isNotEmpty
? cleanedWords[1].characters.first
: '';
final combined = '$firstGrapheme$secondGrapheme'.trim();
if (combined.isEmpty) return 'C';
return combined.toUpperCase();
}
}
static bool _isLeadingPunctuation(String char) {
const punct = {
'-', '_', '.', ',', '!', '?', '@', '#', r'$', '%', '^', '&', '*',
'(', ')', '[', ']', '{', '}', '<', '>', '/', r'\', '|', '~', '`',
'+', '=', ':', ';', '"', '\'', '«', '»', '“', '”', '‘', '’', '•',
};
return punct.contains(char);
}
@override
Widget build(BuildContext context) {
final colors = context.motorColors;
final monogram = getMonogram(name);
final effectiveFontSize = fontSize ?? (size * 0.40);
return Semantics(
label: 'Club monogram for $name',
child: Container(
width: size,
height: size,
decoration: BoxDecoration(
color: backgroundColor ?? colors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(size * 0.28),
border: Border.all(
color: colors.outlineVariant.withValues(alpha: 0.6),
width: 1.0,
),
),
alignment: Alignment.center,
child: Text(
monogram,
style: TextStyle(
fontFamily: AppTypography.fontHeadline,
fontSize: effectiveFontSize,
fontWeight: FontWeight.w800,
letterSpacing: 0.5,
color: textColor ?? colors.cyberCyan,
),
),
),
);
}
}