feat: add carpet
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../../core/config.dart';
|
||||
import '../../../../core/locator/locator.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../core/widgets/game_ui.dart';
|
||||
import '../../../wallet/presentation/bloc/wallet_bloc.dart';
|
||||
import '../../../wallet/presentation/bloc/wallet_event.dart';
|
||||
import '../../../wallet/presentation/bloc/wallet_state.dart';
|
||||
import '../../../wallet/presentation/bloc/wallet_status.dart';
|
||||
import '../../data/data_source/remote/carpet_api_provider.dart';
|
||||
import '../../domain/entities/carpet.dart';
|
||||
import '../../domain/entities/shop_entities.dart';
|
||||
import '../bloc/shop_bloc.dart';
|
||||
import '../bloc/shop_event.dart';
|
||||
@@ -22,7 +24,7 @@ class ShopScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
length: 5,
|
||||
length: 6,
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: GameBackground(
|
||||
@@ -85,6 +87,7 @@ class ShopScreen extends StatelessWidget {
|
||||
_CoinsTab(packages: d.coinPackages),
|
||||
_TicketsTab(packages: d.ticketPackages),
|
||||
_CardsTab(data: d),
|
||||
const _CarpetsTab(),
|
||||
_BoostersTab(boosters: d.boosters),
|
||||
_VipTab(packages: d.vipPackages),
|
||||
],
|
||||
@@ -114,19 +117,7 @@ class ShopScreen extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => context.pop(),
|
||||
child: Container(
|
||||
width: 46,
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.panel,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.gold, width: 1.5),
|
||||
),
|
||||
child: const Icon(Icons.arrow_back, color: AppColors.gold),
|
||||
),
|
||||
),
|
||||
const AppBackButton(),
|
||||
const Spacer(),
|
||||
BlocBuilder<WalletBloc, WalletBlocState>(
|
||||
builder: (context, s) {
|
||||
@@ -164,7 +155,7 @@ class _ShopTabs extends StatelessWidget {
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFC62828), Color(0xFF7B0E14)],
|
||||
colors: [AppColors.panel, AppColors.lapis],
|
||||
),
|
||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)),
|
||||
),
|
||||
@@ -179,6 +170,7 @@ class _ShopTabs extends StatelessWidget {
|
||||
Tab(text: 'سکه'),
|
||||
Tab(text: 'بلیط'),
|
||||
Tab(text: 'کارت'),
|
||||
Tab(text: 'فرش'),
|
||||
Tab(text: 'تجهیزات'),
|
||||
Tab(text: 'VIP'),
|
||||
],
|
||||
@@ -349,6 +341,179 @@ class _CardsTab extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// تبِ فرشها: پیشنمایشِ تصویرِ هر فرش از backend با خرید (سکه/VIP) و انتخاب.
|
||||
/// دادهها مستقل از ShopBloc از /api/carpets گرفته میشوند.
|
||||
class _CarpetsTab extends StatefulWidget {
|
||||
const _CarpetsTab();
|
||||
@override
|
||||
State<_CarpetsTab> createState() => _CarpetsTabState();
|
||||
}
|
||||
|
||||
class _CarpetsTabState extends State<_CarpetsTab> {
|
||||
final _api = locator<CarpetApiProvider>();
|
||||
late Future<CarpetCatalog> _future;
|
||||
String? _busy;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_future = _api.getCarpets();
|
||||
}
|
||||
|
||||
void _reload() => setState(() {
|
||||
_future = _api.getCarpets();
|
||||
});
|
||||
|
||||
Future<void> _act(Carpet c, {required bool buy}) async {
|
||||
setState(() => _busy = c.id);
|
||||
final err = buy ? await _api.buyCarpet(c.id) : await _api.selectCarpet(c.id);
|
||||
if (!mounted) return;
|
||||
setState(() => _busy = null);
|
||||
if (err == null) {
|
||||
context.read<WalletBloc>().add(LoadWalletEvent());
|
||||
_reload();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(content: Text(err)));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<CarpetCatalog>(
|
||||
future: _future,
|
||||
builder: (context, snap) {
|
||||
if (snap.connectionState != ConnectionState.done) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(color: AppColors.gold));
|
||||
}
|
||||
if (!snap.hasData) {
|
||||
return Center(
|
||||
child: TextButton(
|
||||
onPressed: _reload, child: const Text('تلاش مجدد')),
|
||||
);
|
||||
}
|
||||
final cat = snap.data!;
|
||||
return _grid(
|
||||
note: 'فرشِ زیرِ میز را عوض کن؛ بعضی فرشها ویژهی VIP هستند.',
|
||||
children: [
|
||||
for (final c in cat.carpets)
|
||||
_CarpetCard(
|
||||
carpet: c,
|
||||
selected: cat.selected == c.id,
|
||||
busy: _busy == c.id,
|
||||
onSelect: () => _act(c, buy: false),
|
||||
onBuy: () => _act(c, buy: true),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CarpetCard extends StatelessWidget {
|
||||
final Carpet carpet;
|
||||
final bool selected;
|
||||
final bool busy;
|
||||
final VoidCallback onSelect;
|
||||
final VoidCallback onBuy;
|
||||
const _CarpetCard({
|
||||
required this.carpet,
|
||||
required this.selected,
|
||||
required this.busy,
|
||||
required this.onSelect,
|
||||
required this.onBuy,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = carpet;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF6E1322), Color(0xFF3A0A12)],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: selected ? AppColors.gold : AppColors.goldDark,
|
||||
width: selected ? 2 : 1.4),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(c.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.gold, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 6),
|
||||
Expanded(child: _preview(c)),
|
||||
const SizedBox(height: 6),
|
||||
SizedBox(width: double.infinity, child: _action(c)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _preview(Carpet c) {
|
||||
final frame = ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: c.isClassic
|
||||
? Container(
|
||||
color: const Color(0xFF0E3614),
|
||||
child: const Center(
|
||||
child: Icon(Icons.casino, color: AppColors.gold, size: 36)),
|
||||
)
|
||||
: Image.network(
|
||||
c.imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
color: const Color(0xFF2A0710),
|
||||
child: const Icon(Icons.image_not_supported,
|
||||
color: AppColors.goldDark)),
|
||||
),
|
||||
);
|
||||
return Stack(fit: StackFit.expand, children: [
|
||||
frame,
|
||||
if (c.vip && !c.owned)
|
||||
const Positioned(
|
||||
top: 4,
|
||||
left: 4,
|
||||
child: Chip(
|
||||
label: Text('VIP',
|
||||
style: TextStyle(
|
||||
color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)),
|
||||
backgroundColor: AppColors.goldDark,
|
||||
padding: EdgeInsets.zero,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _action(Carpet c) {
|
||||
if (busy) {
|
||||
return const _PriceButton(label: '...', disabled: true);
|
||||
}
|
||||
if (selected) {
|
||||
return const _PriceButton(label: 'انتخاب شده', disabled: true);
|
||||
}
|
||||
if (c.owned) {
|
||||
return _PriceButton(label: 'انتخاب', green: true, onTap: onSelect);
|
||||
}
|
||||
// قفل: VIP رایگان برای VIP (که owned میشود)، وگرنه خرید با سکه.
|
||||
return _PriceButton(
|
||||
label: '${c.priceCoins} سکه',
|
||||
green: true,
|
||||
coin: true,
|
||||
onTap: onBuy,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _grid({required String note, required List<Widget> children}) {
|
||||
return Column(
|
||||
children: [
|
||||
@@ -506,13 +671,16 @@ class _CardSkinArt extends StatelessWidget {
|
||||
child: Image.network(
|
||||
_url(file),
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
color: const Color(0xFF0E3C73),
|
||||
child: const Icon(Icons.style, color: AppColors.gold),
|
||||
),
|
||||
loadingBuilder: (ctx, child, progress) => progress == null
|
||||
? child
|
||||
: Container(color: const Color(0xFF0A2547)),
|
||||
errorBuilder:
|
||||
(_, __, ___) => Container(
|
||||
color: const Color(0xFF0E3C73),
|
||||
child: const Icon(Icons.style, color: AppColors.gold),
|
||||
),
|
||||
loadingBuilder:
|
||||
(ctx, child, progress) =>
|
||||
progress == null
|
||||
? child
|
||||
: Container(color: const Color(0xFF0A2547)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user