Merge pull request 'removed country flags package and read flags from server and implement svg cahce structure' (#117) from flags_cache_svg into main
Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/117
This commit is contained in:
@@ -33,6 +33,10 @@ class AppConfig {
|
||||
// }
|
||||
}
|
||||
|
||||
static String get flagUrl {
|
||||
return 'https://app.opplens.com/api/v1/flags/';
|
||||
}
|
||||
|
||||
// کلیدهای حساس را به این شکل مدیریت کنید
|
||||
static String get apiKey {
|
||||
if (isDevelopment) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'svg_cache.dart';
|
||||
|
||||
class CacheInit {
|
||||
static Future<void> init() async {
|
||||
await SvgCache().init();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SvgCache {
|
||||
static final SvgCache _instance = SvgCache._internal();
|
||||
factory SvgCache() => _instance;
|
||||
SvgCache._internal();
|
||||
|
||||
final Dio _dio = Dio();
|
||||
Directory? _cacheDir;
|
||||
|
||||
Future<void> init() async {
|
||||
if (kIsWeb) {
|
||||
// Web doesn't need directory initialization
|
||||
return;
|
||||
}
|
||||
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
_cacheDir = Directory('${appDir.path}/svg_cache');
|
||||
if (!await _cacheDir!.exists()) {
|
||||
await _cacheDir!.create(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getCachedPath(String url) async {
|
||||
if (kIsWeb) {
|
||||
return await _getWebCachedPath(url);
|
||||
}
|
||||
|
||||
final fileName = _getFileName(url);
|
||||
final file = File('${_cacheDir!.path}/$fileName');
|
||||
return await file.exists() ? file.path : null;
|
||||
}
|
||||
|
||||
Future<String> cacheSvg(String url) async {
|
||||
if (kIsWeb) {
|
||||
return await _cacheWebSvg(url);
|
||||
}
|
||||
|
||||
final fileName = _getFileName(url);
|
||||
final file = File('${_cacheDir!.path}/$fileName');
|
||||
final response = await _dio.get(
|
||||
url,
|
||||
options: Options(responseType: ResponseType.plain),
|
||||
);
|
||||
await file.writeAsString(response.data);
|
||||
return file.path;
|
||||
}
|
||||
|
||||
Future<String?> _getWebCachedPath(String url) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final fileName = _getFileName(url);
|
||||
return prefs.containsKey('svg_cache_$fileName')
|
||||
? 'web_cache_$fileName'
|
||||
: null;
|
||||
}
|
||||
|
||||
Future<String> _cacheWebSvg(String url) async {
|
||||
final response = await _dio.get(
|
||||
url,
|
||||
options: Options(responseType: ResponseType.plain),
|
||||
);
|
||||
final fileName = _getFileName(url);
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// Store SVG data in SharedPreferences for web
|
||||
await prefs.setString('svg_cache_$fileName', response.data);
|
||||
return 'web_cache_$fileName';
|
||||
}
|
||||
|
||||
String _getFileName(String url) {
|
||||
return url.split('/').last.replaceAll(RegExp(r'[^a-zA-Z0-9._-]'), '_');
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:provider/single_child_widget.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tm_app/core/config/dependencies.dart';
|
||||
import 'package:tm_app/core/services/cache_init.dart';
|
||||
import 'package:tm_app/core/theme/theme_provider.dart';
|
||||
|
||||
import 'core/routes/app_routes.dart';
|
||||
@@ -16,6 +17,9 @@ import 'core/utils/logger.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Initialize SVG cache
|
||||
await CacheInit.init();
|
||||
|
||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
|
||||
// Initialize the logger
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:country_flags/country_flags.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:tm_app/core/constants/assets.dart';
|
||||
@@ -6,6 +5,7 @@ import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
|
||||
import '../../../data/services/model/tender_data/tender_data.dart';
|
||||
import '../../shared/flag.dart';
|
||||
import '../strings/tender_details_strings.dart';
|
||||
|
||||
class TenderLocationSection extends StatelessWidget {
|
||||
@@ -48,14 +48,8 @@ class TenderLocationSection extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4.0.w()),
|
||||
SizedBox(
|
||||
width: 32.0.w(),
|
||||
height: 21.0.h(),
|
||||
child: CountryFlag.fromCountryCode(
|
||||
detail.countryCode!,
|
||||
shape: RoundedRectangle(4),
|
||||
),
|
||||
),
|
||||
if (detail.countryCode != null)
|
||||
Flag(countryCode: detail.countryCode!),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8.0.h()),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:country_flags/country_flags.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:tm_app/data/services/model/tender_data/tender_data.dart';
|
||||
@@ -8,6 +7,7 @@ import 'package:tm_app/views/home/widgets/tender_card_progress_bar.dart';
|
||||
import '../../core/constants/assets.dart';
|
||||
import '../../core/theme/colors.dart';
|
||||
import '../../core/utils/size_config.dart';
|
||||
import '../shared/flag.dart';
|
||||
|
||||
class TendersListItem extends StatelessWidget {
|
||||
const TendersListItem({required this.tender, super.key});
|
||||
@@ -113,14 +113,8 @@ class TendersListItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8.0.w()),
|
||||
SizedBox(
|
||||
width: 32.0.w(),
|
||||
height: 21.0.h(),
|
||||
child: CountryFlag.fromCountryCode(
|
||||
tender.countryCode!,
|
||||
shape: RoundedRectangle(4),
|
||||
),
|
||||
),
|
||||
if (tender.countryCode != null)
|
||||
Flag(countryCode: tender.countryCode!),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:country_flags/country_flags.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
@@ -12,6 +11,7 @@ import 'package:tm_app/views/home/strings/home_strings.dart';
|
||||
|
||||
import '../../../core/constants/tender_submision_mode.dart';
|
||||
import '../../../data/services/model/tender_approvals_data/tender_approvals_data.dart';
|
||||
import '../../shared/flag.dart';
|
||||
import '../../shared/select_submission_bottom_sheet.dart';
|
||||
import '../../shared/select_submission_dialog.dart';
|
||||
|
||||
@@ -166,14 +166,8 @@ class LikedListItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8.0.w()),
|
||||
SizedBox(
|
||||
width: 32.0.w(),
|
||||
height: 21.0.h(),
|
||||
child: CountryFlag.fromCountryCode(
|
||||
tender.countryCode!,
|
||||
shape: RoundedRectangle(4),
|
||||
),
|
||||
),
|
||||
if (tender.countryCode != null)
|
||||
Flag(countryCode: tender.countryCode!),
|
||||
Spacer(),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../core/services/svg_cache.dart';
|
||||
|
||||
class CachedSvg extends StatefulWidget {
|
||||
final String url;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final BoxFit fit;
|
||||
|
||||
const CachedSvg({
|
||||
required this.url,
|
||||
this.width,
|
||||
this.height,
|
||||
this.fit = BoxFit.contain,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CachedSvg> createState() => _CachedSvgState();
|
||||
}
|
||||
|
||||
class _CachedSvgState extends State<CachedSvg> {
|
||||
String? _svgData;
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadSvg();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CachedSvg oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.url != widget.url) {
|
||||
_isLoading = true;
|
||||
_svgData = null;
|
||||
_loadSvg();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadSvg() async {
|
||||
try {
|
||||
final cache = SvgCache();
|
||||
String? cachedPath = await cache.getCachedPath(widget.url);
|
||||
cachedPath ??= await cache.cacheSvg(widget.url);
|
||||
if (kIsWeb && cachedPath.startsWith('web_cache_')) {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final fileName = cachedPath.replaceFirst('web_cache_', '');
|
||||
_svgData = prefs.getString('svg_cache_$fileName');
|
||||
} else {
|
||||
_svgData = await File(cachedPath).readAsString();
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading) {
|
||||
return SizedBox(
|
||||
width: widget.width ?? 20,
|
||||
height: widget.height ?? 20,
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
if (_svgData == null) {
|
||||
return const Icon(Icons.error);
|
||||
}
|
||||
|
||||
return SvgPicture.string(
|
||||
_svgData!,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
fit: widget.fit,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
import 'package:tm_app/views/shared/cached_svg.dart';
|
||||
|
||||
import '../../core/config/app_config.dart';
|
||||
|
||||
class Flag extends StatelessWidget {
|
||||
const Flag({required this.countryCode, super.key});
|
||||
|
||||
final String countryCode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return countryCode.isNotEmpty
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: CachedSvg(
|
||||
url: '${AppConfig.flagUrl}$countryCode',
|
||||
width: 32.0.w(),
|
||||
height: 21.0.h(),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
: const SizedBox();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:country_flags/country_flags.dart';
|
||||
import 'package:flutter/material.dart' hide DateUtils;
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -11,6 +10,7 @@ import '../../../core/utils/date_utils.dart';
|
||||
import '../../../core/utils/size_config.dart';
|
||||
import '../../../view_models/tenders_view_model.dart';
|
||||
import '../../detail/strings/tender_details_strings.dart';
|
||||
import '../../shared/flag.dart';
|
||||
import '../strings/tenders_strings.dart';
|
||||
import 'tender_action_buttons_row.dart';
|
||||
|
||||
@@ -253,14 +253,7 @@ class TenderCard extends StatelessWidget {
|
||||
SizedBox(width: 8.0.w()),
|
||||
// Country flag placeholder
|
||||
if (tender.countryCode != null)
|
||||
SizedBox(
|
||||
width: 32.0.w(),
|
||||
height: 21.0.h(),
|
||||
child: CountryFlag.fromCountryCode(
|
||||
tender.countryCode!,
|
||||
shape: RoundedRectangle(4),
|
||||
),
|
||||
),
|
||||
Flag(countryCode: tender.countryCode!),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:country_flags/country_flags.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:tm_app/core/utils/date_utils.dart';
|
||||
@@ -8,6 +7,7 @@ import '../../../core/constants/assets.dart';
|
||||
import '../../../core/constants/tender_approval_status.dart';
|
||||
import '../../../core/theme/colors.dart';
|
||||
import '../../../core/utils/size_config.dart';
|
||||
import '../../shared/flag.dart';
|
||||
|
||||
class TenderCard extends StatelessWidget {
|
||||
final bool isDesktop;
|
||||
@@ -163,15 +163,8 @@ class TenderCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8.0.w()),
|
||||
// Country flag
|
||||
SizedBox(
|
||||
width: 32.0.w(),
|
||||
height: 21.0.h(),
|
||||
child: CountryFlag.fromCountryCode(
|
||||
tender.countryCode!,
|
||||
shape: RoundedRectangle(4),
|
||||
),
|
||||
),
|
||||
if (tender.countryCode != null)
|
||||
Flag(countryCode: tender.countryCode!),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
+1
-1
@@ -23,10 +23,10 @@ dependencies:
|
||||
json_annotation: ^4.8.1
|
||||
http: ^1.5.0
|
||||
intl: ^0.20.2
|
||||
country_flags: ^3.3.0
|
||||
flutter_launcher_icons: ^0.14.4
|
||||
toastification: ^3.0.3
|
||||
pinput: ^5.0.2
|
||||
path_provider: ^2.1.5
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user