feat: add more easter eggs

This commit is contained in:
2026-10-26 23:54:06 +01:00
parent 08f4847800
commit 4d9ba67eb1
18 changed files with 695 additions and 36 deletions
+2 -1
View File
@@ -5,6 +5,7 @@ import '../../l10n/app_localizations.dart';
import '../../models/closed_tab.dart';
import '../../models/closed_tab_item.dart';
import '../../models/payment_method.dart';
import '../../utils/easter_eggs.dart';
import '../../utils/history_grouping.dart';
import '../../utils/money.dart';
@@ -71,7 +72,7 @@ class _ClosedTabCardState extends State<ClosedTabCard> {
children: [
Expanded(
child: Text(
closedTab.customerName,
customerDisplayName(closedTab.customerName),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
+2 -1
View File
@@ -4,6 +4,7 @@ import 'package:intl/intl.dart';
import '../../l10n/app_localizations.dart';
import '../../models/bar_tab.dart';
import '../../models/tab_item_purchase.dart';
import '../../utils/easter_eggs.dart';
import '../../utils/money.dart';
class OpenTabHistoryCard extends StatefulWidget {
@@ -93,7 +94,7 @@ class _OpenTabHistoryCardState extends State<OpenTabHistoryCard> {
children: [
Expanded(
child: Text(
tab.customerName,
customerDisplayName(tab.customerName),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
+96
View File
@@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
/// The bar screen title. Ten quick taps light it up as a flickering neon
/// café sign for a few seconds.
class VenueTitle extends StatefulWidget {
final String text;
const VenueTitle({super.key, required this.text});
@override
State<VenueTitle> createState() => _VenueTitleState();
}
class _VenueTitleState extends State<VenueTitle>
with SingleTickerProviderStateMixin {
static const _tapsNeeded = 10;
static const _tapWindow = Duration(milliseconds: 600);
static const _neonPink = Color(0xFFFF2BD6);
/// Stretches of the animation (as fractions) where the tube is dark,
/// like a sign that sputters on and then dies out.
static const _darkSpans = [
(0.02, 0.05),
(0.07, 0.08),
(0.10, 0.14),
(0.55, 0.56),
(0.88, 0.89),
(0.91, 0.93),
];
late final AnimationController _neon = AnimationController(
vsync: this,
duration: const Duration(seconds: 5),
);
int _tapCount = 0;
DateTime? _lastTap;
@override
void dispose() {
_neon.dispose();
super.dispose();
}
void _onTap() {
final now = DateTime.now();
final last = _lastTap;
_tapCount = last != null && now.difference(last) < _tapWindow
? _tapCount + 1
: 1;
_lastTap = now;
if (_tapCount < _tapsNeeded) return;
_tapCount = 0;
_neon.forward(from: 0);
}
@override
Widget build(BuildContext context) {
final baseStyle = DefaultTextStyle.of(context).style;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: _onTap,
child: AnimatedBuilder(
animation: _neon,
builder: (context, _) {
final t = _neon.value;
final isLit =
_neon.isAnimating &&
!_darkSpans.any((span) => t >= span.$1 && t < span.$2);
return Text(
widget.text,
style: isLit
? baseStyle.copyWith(
color: const Color(0xFFFFD6F6),
fontWeight: FontWeight.w800,
shadows: const [
Shadow(color: _neonPink, blurRadius: 4),
Shadow(color: _neonPink, blurRadius: 12),
Shadow(color: _neonPink, blurRadius: 24),
],
)
: _neon.isAnimating
? baseStyle.copyWith(
color: baseStyle.color?.withValues(alpha: 0.35),
fontWeight: FontWeight.w800,
)
: baseStyle,
);
},
),
);
}
}