feat: show hand result ui
This commit is contained in:
@@ -97,11 +97,17 @@ class _GameScreenState extends State<GameScreen> {
|
|||||||
(a.gameOver == null && b.gameOver != null) ||
|
(a.gameOver == null && b.gameOver != null) ||
|
||||||
(a.handResult == null && b.handResult != null),
|
(a.handResult == null && b.handResult != null),
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
// جشنِ کُت: هنگامِ دستِ کُتشدهی بُرده، ضربهی لمسی.
|
// بازخوردِ لمسیِ بُردِ دست (کُت قویتر) تا کاربر بیدرنگ بفهمد بُرد.
|
||||||
final hr = state.handResult;
|
final hr = state.handResult;
|
||||||
if (hr != null && hr.kot) {
|
if (hr != null) {
|
||||||
final myTeam = (state.state?.yourSeat ?? 0) % 2;
|
final myTeam = (state.state?.yourSeat ?? 0) % 2;
|
||||||
if (hr.winnerTeam == myTeam) HapticFeedback.mediumImpact();
|
if (hr.winnerTeam == myTeam) {
|
||||||
|
hr.kot
|
||||||
|
? HapticFeedback.heavyImpact()
|
||||||
|
: HapticFeedback.mediumImpact();
|
||||||
|
} else {
|
||||||
|
HapticFeedback.lightImpact(); // باختِ دست هم حس شود تا پایانِ دست معلوم باشد
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (state.gameOver != null) {
|
if (state.gameOver != null) {
|
||||||
final won =
|
final won =
|
||||||
@@ -181,7 +187,7 @@ class _GameScreenState extends State<GameScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (state.handResult != null && state.gameOver == null)
|
if (state.handResult != null && state.gameOver == null)
|
||||||
_HandResult(state: state),
|
Positioned.fill(child: _HandResult(state: state)),
|
||||||
if (state.gameOver != null)
|
if (state.gameOver != null)
|
||||||
_GameOver(
|
_GameOver(
|
||||||
state: state,
|
state: state,
|
||||||
|
|||||||
@@ -376,93 +376,225 @@ class _TrumpPicker extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// نتیجهی یک دست (بُرد/باخت/کُت) با جشنِ کُتِ بُرده.
|
/// نتیجهی یک دست با بازخوردِ فوری: آیکنِ بزرگِ رنگی (جام/ضربدر)، واژهی بزرگِ
|
||||||
|
/// سبز/قرمز، واشِ رنگیِ لبههای صفحه و «پاپِ» ورودی تا کاربر بیدرنگ بفهمد بُرد یا
|
||||||
|
/// باخت. کُتِ بُرده جشنِ ویژه (کانفتیِ بیشتر + عنوانِ آتشین) دارد.
|
||||||
class _HandResult extends StatelessWidget {
|
class _HandResult extends StatelessWidget {
|
||||||
final GameUiState state;
|
final GameUiState state;
|
||||||
const _HandResult({required this.state});
|
const _HandResult({required this.state});
|
||||||
|
|
||||||
|
// سبزِ بُرد / قرمزِ باخت (منبعِ رنگِ واش، حاشیه و هاله).
|
||||||
|
static const _winGreen = Color(0xFF3FA34D);
|
||||||
|
static const _loseRed = Color(0xFFE5484D);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final r = state.handResult!;
|
final r = state.handResult!;
|
||||||
final mySeat = state.state?.yourSeat ?? 0;
|
final myTeam = (state.state?.yourSeat ?? 0) % 2;
|
||||||
final myTeam = mySeat % 2;
|
|
||||||
final won = r.winnerTeam == myTeam;
|
final won = r.winnerTeam == myTeam;
|
||||||
final kotWin = won && r.kot; // جشنِ ویژه فقط برای کُتِ بُرده
|
final kotWin = won && r.kot; // جشنِ ویژه فقط برای کُتِ بُرده
|
||||||
|
final accent = won ? _winGreen : _loseRed;
|
||||||
|
final mine = myTeam < r.scores.length ? r.scores[myTeam] : 0;
|
||||||
|
final opp = (1 - myTeam) < r.scores.length ? r.scores[1 - myTeam] : 0;
|
||||||
|
final title = kotWin
|
||||||
|
? (r.hakemKot ? 'حاکمکُت! 🔥' : 'کُت! 🔥')
|
||||||
|
: (won ? 'بردید!' : 'باختید');
|
||||||
|
|
||||||
return IgnorePointer(
|
return IgnorePointer(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
if (kotWin) const Positioned.fill(child: Confetti(count: 40)),
|
// ۱) واشِ رنگیِ لبهها (سبز=بُرد، قرمز=باخت) که سریع ظاهر میشود ⇒ درکِ آنی
|
||||||
|
// حتی با نگاهِ گوشهی چشم.
|
||||||
|
Positioned.fill(
|
||||||
|
child: TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween(begin: 0.0, end: 1.0),
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
builder: (_, v, __) => DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: RadialGradient(
|
||||||
|
radius: 1.15,
|
||||||
|
colors: [
|
||||||
|
Colors.transparent,
|
||||||
|
accent.withValues(alpha: 0.30 * v),
|
||||||
|
],
|
||||||
|
stops: const [0.5, 1.0],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// ۲) کانفتی برای هر بُرد (کُت پرحجمتر).
|
||||||
|
if (won) Positioned.fill(child: Confetti(count: kotWin ? 48 : 22)),
|
||||||
|
// ۳) نشانِ مرکزی با «پاپ» ورودی.
|
||||||
Center(
|
Center(
|
||||||
child: Container(
|
child: TweenAnimationBuilder<double>(
|
||||||
padding:
|
tween: Tween(begin: 0.0, end: 1.0),
|
||||||
const EdgeInsets.symmetric(horizontal: 28, vertical: 18),
|
duration: const Duration(milliseconds: 500),
|
||||||
decoration: BoxDecoration(
|
curve: Curves.easeOutBack,
|
||||||
gradient: kotWin
|
builder: (_, v, child) => Transform.scale(
|
||||||
? const LinearGradient(
|
scale: v.clamp(0.0, 1.12),
|
||||||
colors: [AppColors.lapisMid, AppColors.lapisInk])
|
child: Opacity(opacity: v.clamp(0.0, 1.0), child: child),
|
||||||
: null,
|
|
||||||
color: kotWin ? null : Colors.black87,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border:
|
|
||||||
Border.all(color: AppColors.gold, width: kotWin ? 2 : 1),
|
|
||||||
boxShadow: kotWin
|
|
||||||
? [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColors.gold.withValues(alpha: 0.4),
|
|
||||||
blurRadius: 20),
|
|
||||||
]
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (kotWin)
|
// آیکنِ بزرگِ رنگی: جامِ سبز برای بُرد، ضربدرِ قرمز برای باخت.
|
||||||
Text(
|
Container(
|
||||||
r.hakemKot ? 'حاکمکُت! 🔥' : 'کُت! 🔥',
|
width: 96,
|
||||||
style: const TextStyle(
|
height: 96,
|
||||||
color: AppColors.gold,
|
alignment: Alignment.center,
|
||||||
fontSize: 30,
|
decoration: BoxDecoration(
|
||||||
fontWeight: FontWeight.bold,
|
shape: BoxShape.circle,
|
||||||
shadows: [Shadow(color: Colors.black, blurRadius: 8)],
|
gradient: LinearGradient(
|
||||||
|
colors: won
|
||||||
|
? const [Color(0xFF5FD36B), Color(0xFF1B7A2E)]
|
||||||
|
: const [Color(0xFFF06A6E), Color(0xFF8E1B1F)],
|
||||||
),
|
),
|
||||||
|
border: Border.all(color: Colors.white, width: 3),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: accent.withValues(alpha: 0.65),
|
||||||
|
blurRadius: 26,
|
||||||
|
spreadRadius: 2),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Text(
|
child: Icon(
|
||||||
won ? 'این دست را بردید!' : 'این دست را باختید',
|
won ? Icons.emoji_events_rounded : Icons.close_rounded,
|
||||||
style: TextStyle(
|
color: Colors.white,
|
||||||
color: won ? AppColors.green : Colors.redAccent,
|
size: 52,
|
||||||
fontSize: kotWin ? 16 : 20,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (r.kot && !kotWin)
|
const SizedBox(height: 14),
|
||||||
Padding(
|
// واژهی بزرگِ نتیجه.
|
||||||
padding: const EdgeInsets.only(top: 6),
|
Text(
|
||||||
child: Text(
|
title,
|
||||||
r.hakemKot
|
style: TextStyle(
|
||||||
? 'حاکمکُت! (${r.points} امتیاز)'
|
color: won ? AppColors.gold : const Color(0xFFF06A6E),
|
||||||
: 'کُت! (${r.points} امتیاز)',
|
fontSize: 40,
|
||||||
style: const TextStyle(color: AppColors.gold),
|
fontWeight: FontWeight.bold,
|
||||||
|
shadows: const [
|
||||||
|
Shadow(color: Colors.black, blurRadius: 12)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (kotWin)
|
||||||
|
const Text(
|
||||||
|
'این دست را بردید',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF7BE58A),
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (kotWin)
|
const SizedBox(height: 10),
|
||||||
Text('+${r.points} امتیاز',
|
// چیپِ امتیاز/نتیجه (از دیدِ کاربر: ما · حریف).
|
||||||
style: const TextStyle(color: AppColors.gold)),
|
Container(
|
||||||
const SizedBox(height: 6),
|
padding: const EdgeInsets.symmetric(
|
||||||
Text(
|
horizontal: 16, vertical: 8),
|
||||||
'امتیاز: ${r.scores.isNotEmpty ? r.scores[0] : 0} - ${r.scores.length > 1 ? r.scores[1] : 0}',
|
decoration: BoxDecoration(
|
||||||
style: const TextStyle(color: Colors.white70),
|
color: Colors.black.withValues(alpha: 0.55),
|
||||||
|
borderRadius: BorderRadius.circular(14),
|
||||||
|
border: Border.all(color: accent.withValues(alpha: 0.7)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (won) ...[
|
||||||
|
Text(
|
||||||
|
'+${r.points} امتیاز',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColors.gold,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 1,
|
||||||
|
height: 15,
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
color: Colors.white24,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
Text(
|
||||||
|
'ما $mine · حریف $opp',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white70,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// ۴) عددِ امتیاز از پنلِ امتیاز (بالا-چپ) با پرواز به نشانِ نتیجه میرسد
|
||||||
|
// تا کاربر پیوندِ امتیازِ میز با نتیجهی دست را ببیند — چه بُرد چه باخت.
|
||||||
|
_ScoreFly(points: r.points, won: won),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// عددِ امتیازِ این دست که از پنلِ امتیاز (بالا-چپِ صفحه) بهسمتِ نشانِ نتیجه
|
||||||
|
/// (مرکز) پرواز میکند و آنجا محو میشود — پیوندِ بصریِ امتیازِ میز با نتیجه.
|
||||||
|
/// بُرد: طلایی از سمتِ «ما»؛ باخت: قرمز از سمتِ «حریف».
|
||||||
|
class _ScoreFly extends StatelessWidget {
|
||||||
|
final int points;
|
||||||
|
final bool won;
|
||||||
|
const _ScoreFly({required this.points, required this.won});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// نقطهی شروع نزدیکِ عددِ مربوطه در پنلِ بالا-چپ («ما» راستتر، «حریف» چپتر).
|
||||||
|
final start =
|
||||||
|
won ? const Alignment(-0.68, -0.92) : const Alignment(-0.9, -0.92);
|
||||||
|
final grad = won
|
||||||
|
? const [Color(0xFFFFE082), AppColors.goldDark]
|
||||||
|
: const [Color(0xFFF06A6E), Color(0xFF8E1B1F)];
|
||||||
|
final textColor = won ? AppColors.bg : Colors.white;
|
||||||
|
final glow = won ? AppColors.gold : const Color(0xFFF06A6E);
|
||||||
|
|
||||||
|
return TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween(begin: 0.0, end: 1.0),
|
||||||
|
duration: const Duration(milliseconds: 850),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
builder: (context, t, child) {
|
||||||
|
// مسیرِ حرکت: از عددِ پنل تا کمی بالای نشانِ نتیجه.
|
||||||
|
final align = Alignment.lerp(start, const Alignment(0, -0.22), t)!;
|
||||||
|
// نزدیکِ مقصد بزرگتر میشود و در انتها محو میشود (به نشان میپیوندد).
|
||||||
|
final scale = 0.85 + 0.6 * t;
|
||||||
|
final fade = t < 0.82 ? 1.0 : (1 - (t - 0.82) / 0.18);
|
||||||
|
return Align(
|
||||||
|
alignment: align,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: fade.clamp(0.0, 1.0),
|
||||||
|
child: Transform.scale(scale: scale, child: child),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(colors: grad),
|
||||||
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
border: Border.all(color: Colors.white, width: 1.5),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(color: glow.withValues(alpha: 0.75), blurRadius: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'+$points',
|
||||||
|
style: TextStyle(
|
||||||
|
color: textColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 21,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// اوورلیِ پایانِ بازی (مدال، نتیجه، پاداشها، بازگشت به لابی).
|
/// اوورلیِ پایانِ بازی (مدال، نتیجه، پاداشها، بازگشت به لابی).
|
||||||
class _GameOver extends StatelessWidget {
|
class _GameOver extends StatelessWidget {
|
||||||
final GameUiState state;
|
final GameUiState state;
|
||||||
|
|||||||
Reference in New Issue
Block a user