frosty/lib/utils.dart
2026-06-08 21:35:26 -04:00

103 lines
3.1 KiB
Dart

import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:frosty/constants.dart';
// --- Contrast helpers ---
double _linearize(double c) =>
c <= 0.03928 ? c / 12.92 : math.pow((c + 0.055) / 1.055, 2.4).toDouble();
double _relativeLuminance(Color c) {
final r = _linearize(c.r);
final g = _linearize(c.g);
final b = _linearize(c.b);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
double _contrastRatio(Color a, Color b) {
final l1 = _relativeLuminance(a);
final l2 = _relativeLuminance(b);
final hi = l1 > l2 ? l1 : l2;
final lo = l1 > l2 ? l2 : l1;
return (hi + 0.05) / (lo + 0.05);
}
/// Adjusts [color] just enough to reach [targetContrast] on the app's background.
/// Defaults to Theme.of(context).scaffoldBackgroundColor; if transparent, falls
/// back to theme.colorScheme.surface.
///
/// For normal-size text use 4.5; for large/bold labels you can pass 3.0.
Color adjustChatNameColor(
BuildContext context,
Color color, {
Color? background,
double targetContrast = 4.5,
}) {
final theme = Theme.of(context);
// Pick background: scaffoldBackgroundColor -> surface (if scaffold is transparent).
Color bg = background ?? theme.scaffoldBackgroundColor;
if (bg.a == 0.0) {
// Some themes leave scaffold transparent and paint surfaces; use surface then.
bg = theme.colorScheme.surface;
}
if (_contrastRatio(color, bg) >= targetContrast) return color;
final hsl0 = HSLColor.fromColor(color);
final bool darkBg = _relativeLuminance(bg) < 0.5;
// Binary search lightness toward the needed extreme with gentle sat easing.
double lo = darkBg ? hsl0.lightness : 0.0;
double hi = darkBg ? 1.0 : hsl0.lightness;
double bestL = hsl0.lightness;
for (int i = 0; i < 16; i++) {
final mid = (lo + hi) / 2.0;
final t = (mid - 0.5).abs() * 2; // 0..1 near ends
final easedSat = (hsl0.saturation * (1 - 0.25 * t * t)).clamp(0.0, 1.0);
final candidate = hsl0
.withLightness(mid)
.withSaturation(easedSat)
.toColor();
if (_contrastRatio(candidate, bg) >= targetContrast) {
bestL = mid;
if (darkBg) {
hi = mid; // go brighter, but keep minimal change
} else {
lo = mid; // go darker
}
} else {
if (darkBg) {
lo = mid;
} else {
hi = mid;
}
}
}
final t = (bestL - 0.5).abs() * 2;
final easedSat = (hsl0.saturation * (1 - 0.25 * t * t)).clamp(0.0, 1.0);
return hsl0.withLightness(bestL).withSaturation(easedSat).toColor();
}
String getReadableName(String displayName, String username) {
// If display name equals username, don't add redundant parentheses
if (displayName == username) {
return displayName;
}
// If display name contains only digits, don't add username
if (regexNumbersOnly.hasMatch(displayName)) {
return displayName;
}
// If display name contains English letters, don't add username
if (regexEnglish.hasMatch(displayName)) {
return displayName;
}
// Otherwise (non-English characters, possibly with numbers), add username
return '$displayName ($username)';
}