feat: shop is change

This commit is contained in:
2026-07-05 19:49:59 +03:30
parent e94f03c499
commit df5cc699b6
3 changed files with 109 additions and 63 deletions
+17
View File
@@ -19,6 +19,23 @@ class AppConfig {
defaultValue: '', defaultValue: '',
); );
/// App ID تپسل (از پنلِ تپسل). تا زمانی که تنظیم نشود، تبلیغِ ویدیویی-جایزه‌ای
/// («سکه رایگان») نمایش داده نمی‌شود. با `--dart-define=TAPSELL_APP_ID=...`
/// یا مقداردهیِ پیش‌فرض این‌جا فعال می‌شود.
static const String tapsellAppId = String.fromEnvironment(
'TAPSELL_APP_ID',
defaultValue: '',
);
/// شناسه‌ی جایگاهِ (zone) تبلیغِ ویدیویی-جایزه‌ای تپسل.
static const String tapsellRewardedZoneId = String.fromEnvironment(
'TAPSELL_REWARDED_ZONE',
defaultValue: '',
);
/// آیا تبلیغِ جایزه‌ای فعال است؟ فقط وقتی App ID تپسل موجود باشد.
static bool get adsEnabled => tapsellAppId.isNotEmpty;
/// آدرس پایه‌ی سرور. /// آدرس پایه‌ی سرور.
/// - موبایل واقعی روی همان Wi-Fi: IP محلی مک (پیش‌فرض فعلی). /// - موبایل واقعی روی همان Wi-Fi: IP محلی مک (پیش‌فرض فعلی).
/// - امولاتور اندروید: `--dart-define=BASE_URL=http://10.0.2.2:8080` /// - امولاتور اندروید: `--dart-define=BASE_URL=http://10.0.2.2:8080`
@@ -54,7 +54,7 @@ class ShopScreen extends StatelessWidget {
gradient: const LinearGradient( gradient: const LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [Color(0xFF4A0C16), Color(0xFF2A0710)], colors: [AppColors.lapisMid, AppColors.lapisLo],
), ),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all( border: Border.all(
@@ -155,9 +155,9 @@ class _ShopTabs extends StatelessWidget {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [AppColors.panel, AppColors.lapis], colors: [AppColors.lapis, AppColors.suitDark],
), ),
borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)), borderRadius: BorderRadius.all(Radius.circular(12)),
), ),
indicatorSize: TabBarIndicatorSize.tab, indicatorSize: TabBarIndicatorSize.tab,
labelColor: AppColors.gold, labelColor: AppColors.gold,
@@ -188,17 +188,19 @@ class _CoinsTab extends StatelessWidget {
return _grid( return _grid(
note: 'با داشتن اشتراک VIP در هر خرید ۱۰٪ سکه اضافه هدیه می‌گیرید.', note: 'با داشتن اشتراک VIP در هر خرید ۱۰٪ سکه اضافه هدیه می‌گیرید.',
children: [ children: [
_ItemCard( // «سکه رایگان» (تبلیغِ ویدیویی-جایزه‌ای) فقط وقتی App ID تپسل تنظیم شده باشد.
title: 'سکه رایگان', if (AppConfig.adsEnabled)
glowColor: AppColors.successDark, _ItemCard(
icon: Icons.card_giftcard, title: 'سکه رایگان',
subtitle: 'با دیدن تبلیغ', glowColor: AppColors.successDark,
action: _PriceButton( icon: Icons.card_giftcard,
label: 'رایگان', subtitle: 'با دیدن تبلیغ',
green: true, action: _PriceButton(
onTap: () => context.read<ShopBloc>().add(AdRewardEvent()), label: 'رایگان',
green: true,
onTap: () => context.read<ShopBloc>().add(AdRewardEvent()),
),
), ),
),
for (final p in packages) for (final p in packages)
_ItemCard( _ItemCard(
title: p.title, title: p.title,
@@ -210,8 +212,9 @@ class _CoinsTab extends StatelessWidget {
action: _PriceButton( action: _PriceButton(
label: '${p.priceToman} تومان', label: '${p.priceToman} تومان',
onTap: onTap:
() => () => context.read<ShopBloc>().add(
context.read<ShopBloc>().add(PurchaseEvent('coin', p.id, sku: p.sku)), PurchaseEvent('coin', p.id, sku: p.sku),
),
), ),
), ),
], ],
@@ -291,8 +294,9 @@ class _VipTab extends StatelessWidget {
action: _PriceButton( action: _PriceButton(
label: '${p.priceToman} تومان', label: '${p.priceToman} تومان',
onTap: onTap:
() => () => context.read<ShopBloc>().add(
context.read<ShopBloc>().add(PurchaseEvent('vip', p.id, sku: p.sku)), PurchaseEvent('vip', p.id, sku: p.sku),
),
), ),
), ),
], ],
@@ -361,20 +365,20 @@ class _CarpetsTabState extends State<_CarpetsTab> {
} }
void _reload() => setState(() { void _reload() => setState(() {
_future = _api.getCarpets(); _future = _api.getCarpets();
}); });
Future<void> _act(Carpet c, {required bool buy}) async { Future<void> _act(Carpet c, {required bool buy}) async {
setState(() => _busy = c.id); setState(() => _busy = c.id);
final err = buy ? await _api.buyCarpet(c.id) : await _api.selectCarpet(c.id); final err =
buy ? await _api.buyCarpet(c.id) : await _api.selectCarpet(c.id);
if (!mounted) return; if (!mounted) return;
setState(() => _busy = null); setState(() => _busy = null);
if (err == null) { if (err == null) {
context.read<WalletBloc>().add(LoadWalletEvent()); context.read<WalletBloc>().add(LoadWalletEvent());
_reload(); _reload();
} else { } else {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(err)));
.showSnackBar(SnackBar(content: Text(err)));
} }
} }
@@ -385,12 +389,15 @@ class _CarpetsTabState extends State<_CarpetsTab> {
builder: (context, snap) { builder: (context, snap) {
if (snap.connectionState != ConnectionState.done) { if (snap.connectionState != ConnectionState.done) {
return const Center( return const Center(
child: CircularProgressIndicator(color: AppColors.gold)); child: CircularProgressIndicator(color: AppColors.gold),
);
} }
if (!snap.hasData) { if (!snap.hasData) {
return Center( return Center(
child: TextButton( child: TextButton(
onPressed: _reload, child: const Text('تلاش مجدد')), onPressed: _reload,
child: const Text('تلاش مجدد'),
),
); );
} }
final cat = snap.data!; final cat = snap.data!;
@@ -435,20 +442,25 @@ class _CarpetCard extends StatelessWidget {
gradient: const LinearGradient( gradient: const LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [Color(0xFF6E1322), Color(0xFF3A0A12)], colors: [AppColors.lapisHi, AppColors.lapisLo],
), ),
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all( border: Border.all(
color: selected ? AppColors.gold : AppColors.goldDark, color: selected ? AppColors.gold : AppColors.goldDark,
width: selected ? 2 : 1.4), width: selected ? 2 : 1.4,
),
), ),
child: Column( child: Column(
children: [ children: [
Text(c.title, Text(
maxLines: 1, c.title,
overflow: TextOverflow.ellipsis, maxLines: 1,
style: const TextStyle( overflow: TextOverflow.ellipsis,
color: AppColors.gold, fontWeight: FontWeight.bold)), style: const TextStyle(
color: AppColors.gold,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6), const SizedBox(height: 6),
Expanded(child: _preview(c)), Expanded(child: _preview(c)),
const SizedBox(height: 6), const SizedBox(height: 6),
@@ -461,37 +473,51 @@ class _CarpetCard extends StatelessWidget {
Widget _preview(Carpet c) { Widget _preview(Carpet c) {
final frame = ClipRRect( final frame = ClipRRect(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: c.isClassic child:
? Container( c.isClassic
color: const Color(0xFF0E3614), ? Container(
child: const Center( color: const Color(0xFF0E3614),
child: Icon(Icons.casino, color: AppColors.gold, size: 36)), child: const Center(
) child: Icon(Icons.casino, color: AppColors.gold, size: 36),
: Image.network( ),
c.imageUrl, )
fit: BoxFit.cover, : Image.network(
errorBuilder: (_, __, ___) => Container( c.imageUrl,
color: const Color(0xFF2A0710), fit: BoxFit.cover,
child: const Icon(Icons.image_not_supported, errorBuilder:
color: AppColors.goldDark)), (_, __, ___) => Container(
), color: AppColors.lapisLo,
child: const Icon(
Icons.image_not_supported,
color: AppColors.goldDark,
),
),
),
); );
return Stack(fit: StackFit.expand, children: [ return Stack(
frame, fit: StackFit.expand,
if (c.vip && !c.owned) children: [
const Positioned( frame,
top: 4, if (c.vip && !c.owned)
left: 4, const Positioned(
child: Chip( top: 4,
label: Text('VIP', left: 4,
child: Chip(
label: Text(
'VIP',
style: TextStyle( style: TextStyle(
color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)), color: Colors.white,
backgroundColor: AppColors.goldDark, fontSize: 10,
padding: EdgeInsets.zero, fontWeight: FontWeight.bold,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ),
),
backgroundColor: AppColors.goldDark,
padding: EdgeInsets.zero,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
), ),
), ],
]); );
} }
Widget _action(Carpet c) { Widget _action(Carpet c) {
@@ -567,7 +593,7 @@ class _ItemCard extends StatelessWidget {
gradient: const LinearGradient( gradient: const LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [Color(0xFF6E1322), AppColors.bg], colors: [AppColors.lapisHi, AppColors.lapisLo],
), ),
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.gold, width: 1.6), border: Border.all(color: AppColors.gold, width: 1.6),
@@ -740,7 +766,10 @@ class _PriceButton extends StatelessWidget {
? const [Color(0xFF555555), Color(0xFF333333)] ? const [Color(0xFF555555), Color(0xFF333333)]
: green : green
? const [AppColors.success, AppColors.successDark] ? const [AppColors.success, AppColors.successDark]
: const [Color(0xFFD83A4A), Color(0xFF8E0E1B)]; : const [
AppColors.accent,
Color(0xFF7A1019),
]; // شنگرفِ ایرانی (هم‌رنگِ تم)
return Opacity( return Opacity(
opacity: disabled ? 0.85 : 1, opacity: disabled ? 0.85 : 1,
child: GestureDetector( child: GestureDetector(
@@ -155,7 +155,7 @@ class _VipPackageTile extends StatelessWidget {
gradient: const LinearGradient( gradient: const LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [Color(0xFF6E1322), AppColors.bg], colors: [AppColors.lapisHi, AppColors.lapisLo],
), ),
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.gold, width: 1.4), border: Border.all(color: AppColors.gold, width: 1.4),