feat: add carpet

This commit is contained in:
2026-07-03 21:56:34 +03:30
parent 5160deb973
commit 7acd72c286
21 changed files with 536 additions and 156 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ class AppConfig {
/// با تغییر شبکه/IP مک، مقدار dart-define یا همین پیش‌فرض را عوض کنید.
static const String baseUrl = String.fromEnvironment(
'BASE_URL',
defaultValue: 'http://192.168.8.210:8080',
defaultValue: 'http://192.168.1.105:8080',
);
static String get apiUrl => '$baseUrl/api';
+2
View File
@@ -31,6 +31,7 @@ import '../../feature/ranked/domain/repository/ranked_repository.dart';
import '../../feature/ranked/domain/use_cases/get_leaderboard_usecase.dart';
import '../../feature/ranked/presentation/bloc/leaderboard_bloc.dart';
import '../../feature/shop/data/data_source/remote/shop_api_provider.dart';
import '../../feature/shop/data/data_source/remote/carpet_api_provider.dart';
import '../../feature/shop/data/repository/shop_repository_impl.dart';
import '../../feature/shop/domain/repository/shop_repository.dart';
import '../../feature/shop/domain/use_cases/ad_reward_usecase.dart';
@@ -61,6 +62,7 @@ Future<void> setupLocator() async {
locator.registerSingleton<AuthApiProvider>(AuthApiProvider());
locator.registerSingleton<WalletApiProvider>(WalletApiProvider());
locator.registerSingleton<ShopApiProvider>(ShopApiProvider());
locator.registerSingleton<CarpetApiProvider>(CarpetApiProvider());
locator.registerSingleton<ProfileApiProvider>(ProfileApiProvider());
locator.registerSingleton<RankedApiProvider>(RankedApiProvider());
locator.registerSingleton<ChatApiProvider>(ChatApiProvider());
+16
View File
@@ -57,6 +57,22 @@ class CardImages {
}
}
/// تصویرِ فرش (برای سطحِ میز) از backend: `/carpets/<id>.jpg`. کش‌شده.
static Future<Sprite?> carpet(String id) {
return _cache.putIfAbsent('carpet/$id', () async {
try {
final res = await _dio
.get<List<int>>('${AppConfig.baseUrl}/carpets/$id.jpg');
final data = res.data;
if (data == null || data.isEmpty) return null;
return Sprite(await _decode(Uint8List.fromList(data)));
} catch (e) {
debugPrint('CardImages: carpet/$id failed: $e');
return null;
}
});
}
static Future<ui.Image> _decode(Uint8List bytes) async {
final codec = await ui.instantiateImageCodec(bytes);
final frame = await codec.getNextFrame();
+38 -2
View File
@@ -53,11 +53,17 @@ class AppTheme {
final base = ThemeData.dark(useMaterial3: true);
return base.copyWith(
scaffoldBackgroundColor: AppColors.bg,
primaryColor: AppColors.accent,
primaryColor: AppColors.lapis,
canvasColor: AppColors.bg,
cardColor: AppColors.panel,
dividerColor: AppColors.goldFaint,
colorScheme: base.colorScheme.copyWith(
primary: AppColors.gold,
secondary: AppColors.accent,
onPrimary: AppColors.lapisInk,
secondary: AppColors.lapis,
tertiary: AppColors.accent,
surface: AppColors.panel,
onSurface: AppColors.text,
),
textTheme: base.textTheme.apply(
fontFamily: fontFamily,
@@ -69,6 +75,36 @@ class AppTheme {
foregroundColor: AppColors.gold,
centerTitle: true,
),
// دیالوگ/منو/اسنک‌بار با همان قابِ لاجورد + طلا.
dialogTheme: DialogThemeData(
backgroundColor: AppColors.panel,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
side: const BorderSide(color: AppColors.gold, width: 1.5),
),
titleTextStyle: const TextStyle(
fontFamily: fontFamily,
color: AppColors.gold,
fontSize: 19,
fontWeight: FontWeight.bold),
contentTextStyle:
const TextStyle(fontFamily: fontFamily, color: AppColors.text),
),
snackBarTheme: const SnackBarThemeData(
backgroundColor: AppColors.lapisMid,
contentTextStyle: TextStyle(fontFamily: fontFamily, color: AppColors.text),
behavior: SnackBarBehavior.floating,
),
tabBarTheme: const TabBarThemeData(
labelColor: AppColors.gold,
unselectedLabelColor: Colors.white60,
indicatorColor: AppColors.gold,
),
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: AppColors.panel,
surfaceTintColor: Colors.transparent,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColors.bgDark.withValues(alpha: 0.6),
+33
View File
@@ -1,8 +1,41 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../service/app_sounds.dart';
import '../theme/app_theme.dart';
/// دکمه‌ی بازگشتِ سراسری: قابِ طلایی هماهنگ با تمِ بازی. اگر [onTap] داده نشود،
/// به‌صورت پیش‌فرض یک صفحه به عقب می‌رود (context.pop()).
class AppBackButton extends StatelessWidget {
final VoidCallback? onTap;
final double size;
const AppBackButton({super.key, this.onTap, this.size = 46});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
AppSounds.button();
if (onTap != null) {
onTap!();
} else {
context.pop();
}
},
child: Container(
width: size,
height: size,
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),
),
);
}
}
/// مجموعه‌ی ویجت‌های گرافیکیِ مشترک برای ظاهرِ بازیِ (Unity-style):
/// پس‌زمینه‌ی گرادیانی، پنل براق، دکمه‌ی جواهرگون، و عنوانِ درخشان.