feat: get cards from backend
This commit is contained in:
@@ -8,7 +8,7 @@ class AppConfig {
|
||||
/// با تغییر شبکه/IP مک، مقدار dart-define یا همین پیشفرض را عوض کنید.
|
||||
static const String baseUrl = String.fromEnvironment(
|
||||
'BASE_URL',
|
||||
defaultValue: 'http://192.168.1.105:8080',
|
||||
defaultValue: 'http://192.168.1.104:8080',
|
||||
);
|
||||
|
||||
static String get apiUrl => '$baseUrl/api';
|
||||
|
||||
@@ -24,6 +24,11 @@ import '../../feature/profile/domain/repository/profile_repository.dart';
|
||||
import '../../feature/profile/domain/use_cases/get_profile_usecase.dart';
|
||||
import '../../feature/profile/domain/use_cases/save_profile_usecase.dart';
|
||||
import '../../feature/profile/presentation/bloc/profile_bloc.dart';
|
||||
import '../../feature/ranked/data/data_source/remote/ranked_api_provider.dart';
|
||||
import '../../feature/ranked/data/repository/ranked_repository_impl.dart';
|
||||
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/repository/shop_repository_impl.dart';
|
||||
import '../../feature/shop/domain/repository/shop_repository.dart';
|
||||
@@ -56,6 +61,7 @@ Future<void> setupLocator() async {
|
||||
locator.registerSingleton<WalletApiProvider>(WalletApiProvider());
|
||||
locator.registerSingleton<ShopApiProvider>(ShopApiProvider());
|
||||
locator.registerSingleton<ProfileApiProvider>(ProfileApiProvider());
|
||||
locator.registerSingleton<RankedApiProvider>(RankedApiProvider());
|
||||
locator.registerSingleton<GameApiProvider>(GameApiProvider());
|
||||
locator.registerSingleton<GameWsProvider>(GameWsProvider(locator()));
|
||||
|
||||
@@ -67,6 +73,7 @@ Future<void> setupLocator() async {
|
||||
locator.registerSingleton<ShopRepository>(ShopRepositoryImpl(locator()));
|
||||
locator.registerSingleton<ProfileRepository>(
|
||||
ProfileRepositoryImpl(locator()));
|
||||
locator.registerSingleton<RankedRepository>(RankedRepositoryImpl(locator()));
|
||||
locator.registerSingleton<GameRepository>(
|
||||
GameRepositoryImpl(locator(), locator()));
|
||||
|
||||
@@ -85,6 +92,8 @@ Future<void> setupLocator() async {
|
||||
locator.registerSingleton<AdRewardUseCase>(AdRewardUseCase(locator()));
|
||||
locator.registerSingleton<GetProfileUseCase>(GetProfileUseCase(locator()));
|
||||
locator.registerSingleton<SaveProfileUseCase>(SaveProfileUseCase(locator()));
|
||||
locator.registerSingleton<GetLeaderboardUseCase>(
|
||||
GetLeaderboardUseCase(locator()));
|
||||
locator.registerSingleton<GetTiersUseCase>(GetTiersUseCase(locator()));
|
||||
locator.registerSingleton<GetTablesInfoUseCase>(
|
||||
GetTablesInfoUseCase(locator()));
|
||||
@@ -97,6 +106,7 @@ Future<void> setupLocator() async {
|
||||
ShopBloc(locator(), locator(), locator(), locator(), locator()));
|
||||
locator.registerFactory<ProfileBloc>(
|
||||
() => ProfileBloc(locator(), locator()));
|
||||
locator.registerFactory<LeaderboardBloc>(() => LeaderboardBloc(locator()));
|
||||
locator.registerFactory<GameBloc>(() => GameBloc(locator()));
|
||||
locator.registerFactory<TierBloc>(() => TierBloc(locator()));
|
||||
locator.registerFactory<PrivateInfoBloc>(() => PrivateInfoBloc(locator()));
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../config.dart';
|
||||
|
||||
/// بارگذاریِ تصاویرِ کارت از backend (نه bundle داخل اپ) تا حجم اپ کم بماند.
|
||||
/// هر اسکین یک پوشه روی سرور دارد: `/cards/<skin>/<code>.png` و `/cards/<skin>/back.jpg`.
|
||||
/// تصاویرِ دانلودشده در حافظه کش میشوند تا هر کارت فقط یکبار از شبکه بیاید.
|
||||
class CardImages {
|
||||
CardImages._();
|
||||
|
||||
static const _fallbackSkin = 'simple';
|
||||
static final Dio _dio = Dio(BaseOptions(
|
||||
responseType: ResponseType.bytes,
|
||||
connectTimeout: const Duration(seconds: 10),
|
||||
receiveTimeout: const Duration(seconds: 12),
|
||||
validateStatus: (s) => s != null && s >= 200 && s < 300,
|
||||
));
|
||||
static final Map<String, Future<Sprite?>> _cache = {};
|
||||
|
||||
static String _url(String skin, String file) =>
|
||||
'${AppConfig.baseUrl}/cards/$skin/$file';
|
||||
|
||||
/// اسپرایتِ رویِ یک کارت (مثل "AS"). در صورت خطا/۴۰۴ به اسکینِ پیشفرض و
|
||||
/// سپس به `null` (کشیدنِ برداری در CardComponent) برمیگردد.
|
||||
static Future<Sprite?> front(String skin, String code) =>
|
||||
_load(skin, '$code.png');
|
||||
|
||||
/// اسپرایتِ پشتِ کارت برای اسکین دادهشده.
|
||||
static Future<Sprite?> back(String skin) => _load(skin, 'back.jpg');
|
||||
|
||||
static Future<Sprite?> _load(String skin, String file) {
|
||||
final key = '$skin/$file';
|
||||
return _cache.putIfAbsent(key, () => _fetch(skin, file));
|
||||
}
|
||||
|
||||
static Future<Sprite?> _fetch(String skin, String file) async {
|
||||
final sprite = await _fetchFrom(skin, file);
|
||||
if (sprite != null) return sprite;
|
||||
if (skin != _fallbackSkin) return _fetchFrom(_fallbackSkin, file);
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<Sprite?> _fetchFrom(String skin, String file) async {
|
||||
try {
|
||||
final res = await _dio.get<List<int>>(_url(skin, file));
|
||||
final data = res.data;
|
||||
if (data == null || data.isEmpty) return null;
|
||||
final image = await _decode(Uint8List.fromList(data));
|
||||
return Sprite(image);
|
||||
} catch (e) {
|
||||
debugPrint('CardImages: $skin/$file failed: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<ui.Image> _decode(Uint8List bytes) async {
|
||||
final codec = await ui.instantiateImageCodec(bytes);
|
||||
final frame = await codec.getNextFrame();
|
||||
return frame.image;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// نشانِ رتبه (برنز/نقره/طلا/الماس/پادشاه) با رنگ و برچسبِ فارسی.
|
||||
class RankBadge extends StatelessWidget {
|
||||
final String tier; // bronze..king
|
||||
final int? points; // در صورت نمایش امتیاز
|
||||
final double size;
|
||||
const RankBadge({super.key, required this.tier, this.points, this.size = 16});
|
||||
|
||||
static const _tiers = {
|
||||
'bronze': ('برنز', [Color(0xFFCD7F32), Color(0xFF8C5A2B)]),
|
||||
'silver': ('نقره', [Color(0xFFBFC6CC), Color(0xFF8A949B)]),
|
||||
'gold': ('طلا', [Color(0xFFFFD54F), Color(0xFFB8860B)]),
|
||||
'diamond': ('الماس', [Color(0xFF5BE0E6), Color(0xFF1E8A99)]),
|
||||
'king': ('پادشاه', [Color(0xFFB388FF), Color(0xFF6A1B9A)]),
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = _tiers[tier] ?? _tiers['bronze']!;
|
||||
final label = t.$1;
|
||||
final colors = t.$2;
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: size * 0.6, vertical: size * 0.25),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(colors: colors),
|
||||
borderRadius: BorderRadius.circular(size),
|
||||
boxShadow: const [BoxShadow(color: Colors.black54, blurRadius: 4)],
|
||||
),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Image.asset(
|
||||
'assets/images/badge/$tier.png',
|
||||
width: size * 1.5,
|
||||
height: size * 1.5,
|
||||
errorBuilder: (_, __, ___) =>
|
||||
Icon(Icons.military_tech, color: Colors.white, size: size * 1.1),
|
||||
),
|
||||
SizedBox(width: size * 0.3),
|
||||
Text(
|
||||
points == null ? label : '$label · $points',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: size * 0.85,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user