feat: add sounds

This commit is contained in:
2026-06-18 21:57:28 +03:30
parent 519752b478
commit f4d99043c4
18 changed files with 312 additions and 55 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
"type": "dart", "type": "dart",
"program": "lib/main.dart", "program": "lib/main.dart",
"args": [ "args": [
"--dart-define=BASE_URL=http://192.168.100.6:8080", "--dart-define=BASE_URL=http://192.168.1.105:8080",
"--target-platform=android-arm64" "--target-platform=android-arm64"
] ]
}, },
+1 -1
View File
@@ -8,7 +8,7 @@ class AppConfig {
/// با تغییر شبکه/IP مک، مقدار dart-define یا همین پیش‌فرض را عوض کنید. /// با تغییر شبکه/IP مک، مقدار dart-define یا همین پیش‌فرض را عوض کنید.
static const String baseUrl = String.fromEnvironment( static const String baseUrl = String.fromEnvironment(
'BASE_URL', 'BASE_URL',
defaultValue: 'http://192.168.100.6:8080', defaultValue: 'http://192.168.1.105:8080',
); );
static String get apiUrl => '$baseUrl/api'; static String get apiUrl => '$baseUrl/api';
+51
View File
@@ -0,0 +1,51 @@
import 'package:flame_audio/flame_audio.dart';
/// سرویسِ متمرکزِ افکت‌های صوتی (فایل‌ها در assets/audio/).
///
/// از AudioPool استفاده می‌شود: برای هر صدا یک مجموعه‌ی محدود از پخش‌کننده‌ها
/// ساخته و بازاستفاده می‌شود. این کار از نشتِ AudioPlayerها (که روی اندروید
/// باعثِ قطعِ کاملِ صدا و پرشِ فریم می‌شد) جلوگیری می‌کند.
class AppSounds {
AppSounds._();
static const _shuffle = 'shuffle.mp3'; // بُر زدن/پخشِ کارت‌ها (شروع دست)
static const _placeCard = 'place_card.mp3'; // گذاشتنِ کارت روی میز (همه‌ی بازیکنان)
static const _takeCard = 'take_card.mp3'; // جمع‌شدنِ دستِ کامل
static const _cut = 'cut.mp3'; // بریدن با حکم (شمشیر)
static const _win = 'win.mp3'; // برد بازی
static const _button = 'button.mp3'; // فشردنِ دکمه‌ها
static const _all = [_shuffle, _placeCard, _takeCard, _cut, _win, _button];
static final Map<String, AudioPool> _pools = {};
static bool muted = false;
/// ساختِ Poolها (یک‌بار، در main). تا آماده‌شدن، پخش‌ها بی‌اثرند.
static Future<void> preload() async {
for (final f in _all) {
try {
_pools[f] = await FlameAudio.createPool(f, maxPlayers: 3);
} catch (_) {/* نبودِ فایل ⇒ بی‌صدا */}
}
}
/// پخش از Pool؛ اگر cutoffMs داده شود، صدا پس از آن مدت قطع می‌شود (کلیکِ کوتاه).
static void _play(String file, double volume, {int? cutoffMs}) {
if (muted) return;
final pool = _pools[file];
if (pool == null) return;
pool.start(volume: volume).then((stop) {
if (cutoffMs != null) {
Future.delayed(Duration(milliseconds: cutoffMs), () => stop());
}
}).catchError((_) {});
}
/// صدای بُر زدن؛ cutoffMs را برابرِ مدتِ انیمیشنِ بُر بده تا هم‌زمان تمام شوند.
static void shuffle({int? cutoffMs}) => _play(_shuffle, 0.7, cutoffMs: cutoffMs);
static void placeCard() => _play(_placeCard, 0.8);
static void takeCard() => _play(_takeCard, 0.7);
static void cut() => _play(_cut, 0.9);
static void win() => _play(_win, 0.9);
static void button() => _play(_button, 0.5, cutoffMs: 130); // کلیکِ کوتاه
}
+7 -1
View File
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../service/app_sounds.dart';
import '../theme/app_theme.dart'; import '../theme/app_theme.dart';
/// مجموعه‌ی ویجت‌های گرافیکیِ مشترک برای ظاهرِ بازیِ (Unity-style): /// مجموعه‌ی ویجت‌های گرافیکیِ مشترک برای ظاهرِ بازیِ (Unity-style):
@@ -111,7 +112,12 @@ class _GameButtonState extends State<GameButton> {
onTapDown: enabled ? (_) => setState(() => _down = true) : null, onTapDown: enabled ? (_) => setState(() => _down = true) : null,
onTapUp: enabled ? (_) => setState(() => _down = false) : null, onTapUp: enabled ? (_) => setState(() => _down = false) : null,
onTapCancel: enabled ? () => setState(() => _down = false) : null, onTapCancel: enabled ? () => setState(() => _down = false) : null,
onTap: widget.onTap, onTap: enabled
? () {
AppSounds.button();
widget.onTap!();
}
: null,
child: AnimatedScale( child: AnimatedScale(
scale: _down ? 0.96 : 1.0, scale: _down ? 0.96 : 1.0,
duration: const Duration(milliseconds: 90), duration: const Duration(milliseconds: 90),
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:pinput/pinput.dart';
import '../../../../core/theme/app_theme.dart'; import '../../../../core/theme/app_theme.dart';
import '../../../../core/widgets/game_ui.dart'; import '../../../../core/widgets/game_ui.dart';
@@ -10,7 +10,7 @@ import '../bloc/auth_event.dart';
import '../bloc/auth_state.dart'; import '../bloc/auth_state.dart';
import '../bloc/otp_status.dart'; import '../bloc/otp_status.dart';
/// صفحه‌ی ورود کد یک‌بارمصرف (۵ رقمی). /// صفحه‌ی ورود کد یک‌بارمصرف (۵ رقمی) با pinput و تکمیل خودکارِ پیامک.
class OtpScreen extends StatefulWidget { class OtpScreen extends StatefulWidget {
const OtpScreen({super.key}); const OtpScreen({super.key});
@@ -29,8 +29,26 @@ class _OtpScreenState extends State<OtpScreen> {
bool get _valid => _controller.text.trim().length == 5; bool get _valid => _controller.text.trim().length == 5;
void _submit(BuildContext context) {
if (_valid) {
context.read<AuthBloc>().add(CheckOtpEvent(_controller.text.trim()));
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final defaultPin = PinTheme(
width: 52,
height: 60,
textStyle: const TextStyle(
color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
decoration: BoxDecoration(
color: AppColors.bgDark,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColors.goldDark, width: 1.5),
),
);
return Scaffold( return Scaffold(
body: GameBackground( body: GameBackground(
child: BlocConsumer<AuthBloc, AuthState>( child: BlocConsumer<AuthBloc, AuthState>(
@@ -62,31 +80,39 @@ class _OtpScreenState extends State<OtpScreen> {
Text('کد پیامک‌شده به ${state.mobile} را وارد کنید', Text('کد پیامک‌شده به ${state.mobile} را وارد کنید',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white70)), style: const TextStyle(color: Colors.white70)),
const SizedBox(height: 20), const SizedBox(height: 22),
TextField( Directionality(
controller: _controller, textDirection: TextDirection.ltr,
keyboardType: TextInputType.number, child: Pinput(
textAlign: TextAlign.center, length: 5,
style: const TextStyle( controller: _controller,
fontSize: 28, letterSpacing: 12), autofocus: true,
inputFormatters: [ keyboardType: TextInputType.number,
FilteringTextInputFormatter.digitsOnly, closeKeyboardWhenCompleted: true,
LengthLimitingTextInputFormatter(5), defaultPinTheme: defaultPin,
], focusedPinTheme: defaultPin.copyWith(
decoration: decoration: defaultPin.decoration!.copyWith(
const InputDecoration(hintText: '- - - - -'), border:
onChanged: (_) => setState(() {}), Border.all(color: AppColors.gold, width: 2),
),
),
separatorBuilder: (_) => const SizedBox(width: 8),
pinAnimationType: PinAnimationType.fade,
// تکمیل خودکارِ کد از پیامک (Android: SMS User Consent).
androidSmsAutofillMethod:
AndroidSmsAutofillMethod.smsUserConsentApi,
onChanged: (_) => setState(() {}),
onCompleted: (_) => _submit(context),
),
), ),
const SizedBox(height: 20), const SizedBox(height: 22),
GameButton( GameButton(
label: 'ورود', label: 'ورود',
width: double.infinity, width: double.infinity,
colors: const [Color(0xFF3FA34D), Color(0xFF1B5E20)], colors: const [Color(0xFF3FA34D), Color(0xFF1B5E20)],
onTap: (!_valid || loading) onTap: (!_valid || loading)
? null ? null
: () => context : () => _submit(context),
.read<AuthBloc>()
.add(CheckOtpEvent(_controller.text.trim())),
), ),
TextButton( TextButton(
onPressed: loading ? null : () => context.pop(), onPressed: loading ? null : () => context.pop(),
@@ -6,6 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../../../core/network/ws_client.dart'; import '../../../../core/network/ws_client.dart';
import '../../../../core/service/app_sounds.dart';
import '../../../../core/theme/app_theme.dart'; import '../../../../core/theme/app_theme.dart';
import '../../../wallet/presentation/bloc/wallet_bloc.dart'; import '../../../wallet/presentation/bloc/wallet_bloc.dart';
import '../../../wallet/presentation/bloc/wallet_event.dart'; import '../../../wallet/presentation/bloc/wallet_event.dart';
@@ -51,19 +52,32 @@ class _GameScreenState extends State<GameScreen> {
}, },
child: Scaffold( child: Scaffold(
body: BlocConsumer<GameBloc, GameUiState>( body: BlocConsumer<GameBloc, GameUiState>(
listenWhen: (a, b) => a.notice != b.notice && b.notice != null, listenWhen: (a, b) =>
(a.notice != b.notice && b.notice != null) ||
(a.gameOver == null && b.gameOver != null),
listener: (context, state) { listener: (context, state) {
ScaffoldMessenger.of(context).showSnackBar( if (state.gameOver != null) {
SnackBar( final won = state.gameOver!.winnerTeam ==
content: Text(state.notice!), (state.state?.yourSeat ?? 0) % 2;
duration: const Duration(seconds: 2)), if (won) AppSounds.win();
); }
context.read<GameBloc>().clearNotice(); if (state.notice != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.notice!),
duration: const Duration(seconds: 2)),
);
context.read<GameBloc>().clearNotice();
}
}, },
builder: (context, state) { builder: (context, state) {
if (state.state != null && _introTimer == null) { if (state.state != null && _introTimer == null) {
_introTimer = Timer(const Duration(milliseconds: 1600), () { // کمی «حریفان پیدا شد» نشان بده، بعد اوورلی را کنار بزن و همان
if (mounted) setState(() => _introHidden = true); // لحظه انیمیشنِ بُر زدن (با صدا) را اجرا کن.
_introTimer = Timer(const Duration(milliseconds: 700), () {
if (!mounted) return;
setState(() => _introHidden = true);
_game.endIntro();
}); });
} }
return Stack( return Stack(
@@ -130,6 +144,7 @@ class _GameScreenState extends State<GameScreen> {
} }
void _exitToLobby(BuildContext context) { void _exitToLobby(BuildContext context) {
_game.silence(); // پس از خروج، صدای کارت‌های بات پخش نشود
context.read<WalletBloc>().add(LoadWalletEvent()); context.read<WalletBloc>().add(LoadWalletEvent());
context.go('/lobby'); context.go('/lobby');
} }
@@ -4,13 +4,14 @@ import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/effects.dart'; import 'package:flame/effects.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flame_audio/flame_audio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../../core/service/app_sounds.dart';
import '../../../domain/entities/game_entities.dart'; import '../../../domain/entities/game_entities.dart';
import '../../bloc/game_bloc.dart'; import '../../bloc/game_bloc.dart';
import 'card_codes.dart'; import 'card_codes.dart';
import 'card_component.dart'; import 'card_component.dart';
import 'shuffle_animation.dart';
import 'table_pieces.dart'; import 'table_pieces.dart';
/// صحنه‌ی میز حکم با انیمیشن. شما همیشه پایین میز (rel=0) هستید. /// صحنه‌ی میز حکم با انیمیشن. شما همیشه پایین میز (rel=0) هستید.
@@ -42,6 +43,9 @@ class HokmGame extends FlameGame {
double _t = 0; double _t = 0;
List<String> _prevTrick = const []; List<String> _prevTrick = const [];
int _prevHandSize = 0; // برای تشخیصِ پخشِ کارت (دستِ جدید) جهت صدای بُر زدن int _prevHandSize = 0; // برای تشخیصِ پخشِ کارت (دستِ جدید) جهت صدای بُر زدن
bool _silent = false; // هنگام خروج از میز، دیگر صدایی پخش نشود
bool _introActive = true; // تا پایانِ نمایشِ «جستجوی حریف»
bool _pendingShuffle = false; // بُرِ دستِ اول که تا پایانِ اینترو به تعویق افتاده
HokmGame(this.cubit); HokmGame(this.cubit);
@@ -51,14 +55,20 @@ class HokmGame extends FlameGame {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
add(Felt()); add(Felt());
// پیش‌بارگذاریِ صدای بُر زدن (در صورت نبودِ فایل، بی‌صدا رد می‌شود).
FlameAudio.audioCache.load('shuffle.mp3').catchError((_) => Uri());
_sub = cubit.stream.listen((ui) { _sub = cubit.stream.listen((ui) {
if (ui.state == null) return; if (ui.state == null) return;
final s = ui.state!; final s = ui.state!;
_checkCut(s); _checkTrickSounds(s);
// شروعِ دستِ جدید (پخشِ کارت): صدای بُر زدن. // شروعِ دستِ جدید (پخشِ کارت): انیمیشن و صدای بُر زدن.
if (_prevHandSize == 0 && s.yourHand.isNotEmpty) _playShuffle(); // دستِ اول تا پایانِ نمایشِ «جستجوی حریف» به تعویق می‌افتد تا انیمیشن
// زیرِ اوورلی پنهان نشود و صدا با تصویر هماهنگ باشد.
if (_prevHandSize == 0 && s.yourHand.isNotEmpty && !_silent) {
if (_introActive) {
_pendingShuffle = true;
} else {
_shuffleNow();
}
}
_prevHandSize = s.yourHand.length; _prevHandSize = s.yourHand.length;
_s = s; _s = s;
_relayout(); _relayout();
@@ -71,11 +81,6 @@ class HokmGame extends FlameGame {
} }
} }
void _playShuffle() {
// اگر فایل صدا نباشد، خطا نادیده گرفته می‌شود (بازی بی‌صدا).
FlameAudio.play('shuffle.mp3', volume: 0.7).then((_) {}, onError: (_) {});
}
@override @override
void onGameResize(Vector2 size) { void onGameResize(Vector2 size) {
super.onGameResize(size); super.onGameResize(size);
@@ -88,24 +93,57 @@ class HokmGame extends FlameGame {
super.onRemove(); super.onRemove();
} }
// ===== افکتِ بریدن با حکم (تکان + رعد) ===== // ===== صداها و افکتِ بریدن با حکم (تکان + رعد) =====
// اگر دقیقاً یک کارتِ تازه به زمین اضافه شده و آن کارت «بُرِش با حکم» باشد // با هر تغییرِ زمین: افزودنِ کارت ⇒ صدای گذاشتن/انداختن (و بریدن با حکم
// (خالِ زمینه آتو نیست ولی کارتِ تازه آتوست)، همان لحظه تکان + رعد. // شمشیر + تکان + رعد)؛ خالی‌شدنِ زمین ⇒ صدای جمع‌شدنِ دست.
void _checkCut(GameState s) { void _checkTrickSounds(GameState s) {
final now = s.trick.map((t) => t.card).toList(); final now = s.trick.map((t) => t.card).toList();
final addedOne = now.length == _prevTrick.length + 1 && _isPrefix(_prevTrick, now); if (_silent) {
if (addedOne && _prevTrick = now;
s.trump != null && return;
s.leadSuit != null && }
s.leadSuit != s.trump && final addedOne =
suitName(now.last) == s.trump) { now.length == _prevTrick.length + 1 && _isPrefix(_prevTrick, now);
_shake = 1.0; if (addedOne) {
add(Lightning()); final card = s.trick.last;
final isCut = s.trump != null &&
s.leadSuit != null &&
s.leadSuit != s.trump &&
suitName(card.card) == s.trump;
if (isCut) {
_shake = 1.0;
add(Lightning());
AppSounds.cut(); // بریدن با حکم (شمشیر)
} else {
AppSounds.placeCard(); // کارت روی میز نشست (همه‌ی بازیکنان)
}
} else if (now.isEmpty && _prevTrick.isNotEmpty) {
AppSounds.takeCard(); // دستِ کامل جمع شد
} }
_prevTrick = now; _prevTrick = now;
} }
/// قطعِ صداهای بازی هنگام خروج از میز (جلوگیری از پخشِ صدای کارت پس از ترک).
void silence() => _silent = true;
/// پایانِ نمایشِ «جستجوی حریف»؛ اگر بُرِ دستِ اول به تعویق افتاده بود، اکنون
/// (که اوورلی کنار رفته) با صدا و تصویرِ هماهنگ اجرا می‌شود.
void endIntro() {
_introActive = false;
if (_pendingShuffle && !_silent) {
_pendingShuffle = false;
_shuffleNow();
}
}
/// اجرای هم‌زمانِ انیمیشن و صدای بُر زدن (صدا با پایانِ انیمیشن قطع می‌شود).
void _shuffleNow() {
if (_silent) return;
AppSounds.shuffle(cutoffMs: ShuffleAnimation.totalMs);
add(ShuffleAnimation(size));
}
static bool _isPrefix(List<String> a, List<String> b) { static bool _isPrefix(List<String> a, List<String> b) {
for (var i = 0; i < a.length; i++) { for (var i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false; if (a[i] != b[i]) return false;
@@ -0,0 +1,94 @@
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flutter/material.dart';
/// انیمیشنِ بُر زدن: دو دسته کارتِ پشت‌رو در مرکز ریفل می‌شوند، کمی بالا/پایین
/// می‌روند (بُر) و سپس به سمت چهار بازیکن پخش می‌شوند. مدتِ کل [totalMs] است
/// تا صدای بُر زدن دقیقاً هم‌زمان با پایانِ انیمیشن قطع شود.
class ShuffleAnimation extends PositionComponent {
final Vector2 area;
ShuffleAnimation(this.area);
/// مدتِ کلِ انیمیشن (مرجعِ هماهنگیِ صدا).
static const int totalMs = 2000;
@override
Future<void> onLoad() async {
priority = 6000;
final cw = area.x * 0.16;
final ch = cw * 1.45;
final center = Vector2(area.x / 2, area.y / 2);
const n = 10;
// جهتِ پخش به سمتِ ۴ بازیکن (پایین، چپ، بالا، راست).
final dirs = [
Vector2(0, area.y * 0.42),
Vector2(-area.x * 0.42, 0),
Vector2(0, -area.y * 0.42),
Vector2(area.x * 0.42, 0),
];
for (var i = 0; i < n; i++) {
final left = i.isEven;
final stack = center + Vector2((i - n / 2) * cw * 0.05, 0);
final delay = i * 0.02; // ریفلِ پلکانی
final card = _Back(Vector2(cw, ch))
..anchor = Anchor.center
..position = center + Vector2((left ? -1 : 1) * cw, 0)
..angle = (left ? -1 : 1) * 0.22;
add(card);
// صاف‌شدنِ زاویه هم‌زمان با ریفل.
card.add(RotateEffect.to(
0, EffectController(duration: 0.55, startDelay: delay)));
// دنباله: ریفل به مرکز ← بالا (بُر) ← نشستن ← پخش به سمت بازیکن.
card.add(SequenceEffect([
MoveToEffect(stack,
EffectController(duration: 0.55, curve: Curves.easeOut, startDelay: delay)),
MoveToEffect(stack + Vector2(0, -ch * 0.18),
EffectController(duration: 0.35, curve: Curves.easeInOut)),
MoveToEffect(stack,
EffectController(duration: 0.35, curve: Curves.easeInOut)),
MoveToEffect(center + dirs[i % 4],
EffectController(duration: 0.55, curve: Curves.easeIn)),
]));
// کوچک‌شدن هنگامِ پخش (هم‌زمان با حرکتِ آخرِ دنباله).
card.add(ScaleEffect.to(
Vector2.all(0.5),
EffectController(duration: 0.55, startDelay: 1.25 + delay),
));
}
add(RemoveEffect(delay: totalMs / 1000));
}
}
/// پشتِ یک کارت (مستطیلِ گرد با حاشیه‌ی طلایی).
class _Back extends PositionComponent {
_Back(Vector2 s) : super(size: s);
static final _fill = Paint()..color = const Color(0xFF7A1020);
static final _border = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2
..color = const Color(0xFFE9C76A);
static final _inner = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1.5
..color = const Color(0x88E9C76A);
@override
void render(Canvas canvas) {
final rect = size.toRect();
canvas.drawRRect(
RRect.fromRectAndRadius(rect, const Radius.circular(8)), _fill);
canvas.drawRRect(
RRect.fromRectAndRadius(rect, const Radius.circular(8)), _border);
canvas.drawRRect(
RRect.fromRectAndRadius(rect.deflate(6), const Radius.circular(6)),
_inner);
}
}
+2
View File
@@ -2,11 +2,13 @@ import 'package:flutter/material.dart';
import 'app.dart'; import 'app.dart';
import 'core/locator/locator.dart'; import 'core/locator/locator.dart';
import 'core/service/app_sounds.dart';
import 'feature/auth/domain/repository/auth_repository.dart'; import 'feature/auth/domain/repository/auth_repository.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await setupLocator(); await setupLocator();
AppSounds.preload(); // پیش‌بارگذاریِ صداها (بدون انتظار)
final loggedIn = await locator<AuthRepository>().isLoggedIn(); final loggedIn = await locator<AuthRepository>().isLoggedIn();
runApp(HakemApp(loggedIn: loggedIn)); runApp(HakemApp(loggedIn: loggedIn));
} }
+24
View File
@@ -480,6 +480,14 @@ packages:
url: "https://pub.myket.ir" url: "https://pub.myket.ir"
source: hosted source: hosted
version: "7.0.2" version: "7.0.2"
pinput:
dependency: "direct main"
description:
name: pinput
sha256: "6d571e38a484f7515a52e89024ef416f11fa6171ac6f32303701374ab9890efa"
url: "https://pub.myket.ir"
source: hosted
version: "4.0.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -517,6 +525,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
smart_auth:
dependency: transitive
description:
name: smart_auth
sha256: "88aa8fe66e951c78a307f26d1c29672dce2e9eb3da2e12e853864d0e615a73ad"
url: "https://pub.myket.ir"
source: hosted
version: "2.0.0"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@@ -581,6 +597,14 @@ packages:
url: "https://pub.myket.ir" url: "https://pub.myket.ir"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.myket.ir"
source: hosted
version: "1.1.0"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:
+1
View File
@@ -41,6 +41,7 @@ dependencies:
flutter_secure_storage: ^10.3.1 flutter_secure_storage: ^10.3.1
equatable: ^2.0.8 equatable: ^2.0.8
get_it: ^8.0.3 get_it: ^8.0.3
pinput: ^4.0.0
flame: ^1.30.1 flame: ^1.30.1
flame_audio: ^2.11.14 flame_audio: ^2.11.14
random_avatar: ^0.0.8 random_avatar: ^0.0.8