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 {
|
static String get apiKey {
|
||||||
if (isDevelopment) {
|
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:provider/single_child_widget.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:tm_app/core/config/dependencies.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 'package:tm_app/core/theme/theme_provider.dart';
|
||||||
|
|
||||||
import 'core/routes/app_routes.dart';
|
import 'core/routes/app_routes.dart';
|
||||||
@@ -16,6 +17,9 @@ import 'core/utils/logger.dart';
|
|||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// Initialize SVG cache
|
||||||
|
await CacheInit.init();
|
||||||
|
|
||||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
|
|
||||||
// Initialize the logger
|
// Initialize the logger
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:country_flags/country_flags.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:tm_app/core/constants/assets.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 'package:tm_app/core/utils/size_config.dart';
|
||||||
|
|
||||||
import '../../../data/services/model/tender_data/tender_data.dart';
|
import '../../../data/services/model/tender_data/tender_data.dart';
|
||||||
|
import '../../shared/flag.dart';
|
||||||
import '../strings/tender_details_strings.dart';
|
import '../strings/tender_details_strings.dart';
|
||||||
|
|
||||||
class TenderLocationSection extends StatelessWidget {
|
class TenderLocationSection extends StatelessWidget {
|
||||||
@@ -48,14 +48,8 @@ class TenderLocationSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 4.0.w()),
|
SizedBox(width: 4.0.w()),
|
||||||
SizedBox(
|
if (detail.countryCode != null)
|
||||||
width: 32.0.w(),
|
Flag(countryCode: detail.countryCode!),
|
||||||
height: 21.0.h(),
|
|
||||||
child: CountryFlag.fromCountryCode(
|
|
||||||
detail.countryCode!,
|
|
||||||
shape: RoundedRectangle(4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 8.0.h()),
|
SizedBox(height: 8.0.h()),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:country_flags/country_flags.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:tm_app/data/services/model/tender_data/tender_data.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/constants/assets.dart';
|
||||||
import '../../core/theme/colors.dart';
|
import '../../core/theme/colors.dart';
|
||||||
import '../../core/utils/size_config.dart';
|
import '../../core/utils/size_config.dart';
|
||||||
|
import '../shared/flag.dart';
|
||||||
|
|
||||||
class TendersListItem extends StatelessWidget {
|
class TendersListItem extends StatelessWidget {
|
||||||
const TendersListItem({required this.tender, super.key});
|
const TendersListItem({required this.tender, super.key});
|
||||||
@@ -113,14 +113,8 @@ class TendersListItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 8.0.w()),
|
SizedBox(width: 8.0.w()),
|
||||||
SizedBox(
|
if (tender.countryCode != null)
|
||||||
width: 32.0.w(),
|
Flag(countryCode: tender.countryCode!),
|
||||||
height: 21.0.h(),
|
|
||||||
child: CountryFlag.fromCountryCode(
|
|
||||||
tender.countryCode!,
|
|
||||||
shape: RoundedRectangle(4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(),
|
Spacer(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:country_flags/country_flags.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.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 '../../../core/constants/tender_submision_mode.dart';
|
||||||
import '../../../data/services/model/tender_approvals_data/tender_approvals_data.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_bottom_sheet.dart';
|
||||||
import '../../shared/select_submission_dialog.dart';
|
import '../../shared/select_submission_dialog.dart';
|
||||||
|
|
||||||
@@ -166,14 +166,8 @@ class LikedListItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 8.0.w()),
|
SizedBox(width: 8.0.w()),
|
||||||
SizedBox(
|
if (tender.countryCode != null)
|
||||||
width: 32.0.w(),
|
Flag(countryCode: tender.countryCode!),
|
||||||
height: 21.0.h(),
|
|
||||||
child: CountryFlag.fromCountryCode(
|
|
||||||
tender.countryCode!,
|
|
||||||
shape: RoundedRectangle(4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(),
|
Spacer(),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () {
|
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/material.dart' hide DateUtils;
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -11,6 +10,7 @@ import '../../../core/utils/date_utils.dart';
|
|||||||
import '../../../core/utils/size_config.dart';
|
import '../../../core/utils/size_config.dart';
|
||||||
import '../../../view_models/tenders_view_model.dart';
|
import '../../../view_models/tenders_view_model.dart';
|
||||||
import '../../detail/strings/tender_details_strings.dart';
|
import '../../detail/strings/tender_details_strings.dart';
|
||||||
|
import '../../shared/flag.dart';
|
||||||
import '../strings/tenders_strings.dart';
|
import '../strings/tenders_strings.dart';
|
||||||
import 'tender_action_buttons_row.dart';
|
import 'tender_action_buttons_row.dart';
|
||||||
|
|
||||||
@@ -253,14 +253,7 @@ class TenderCard extends StatelessWidget {
|
|||||||
SizedBox(width: 8.0.w()),
|
SizedBox(width: 8.0.w()),
|
||||||
// Country flag placeholder
|
// Country flag placeholder
|
||||||
if (tender.countryCode != null)
|
if (tender.countryCode != null)
|
||||||
SizedBox(
|
Flag(countryCode: tender.countryCode!),
|
||||||
width: 32.0.w(),
|
|
||||||
height: 21.0.h(),
|
|
||||||
child: CountryFlag.fromCountryCode(
|
|
||||||
tender.countryCode!,
|
|
||||||
shape: RoundedRectangle(4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:country_flags/country_flags.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:tm_app/core/utils/date_utils.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/constants/tender_approval_status.dart';
|
||||||
import '../../../core/theme/colors.dart';
|
import '../../../core/theme/colors.dart';
|
||||||
import '../../../core/utils/size_config.dart';
|
import '../../../core/utils/size_config.dart';
|
||||||
|
import '../../shared/flag.dart';
|
||||||
|
|
||||||
class TenderCard extends StatelessWidget {
|
class TenderCard extends StatelessWidget {
|
||||||
final bool isDesktop;
|
final bool isDesktop;
|
||||||
@@ -163,15 +163,8 @@ class TenderCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 8.0.w()),
|
SizedBox(width: 8.0.w()),
|
||||||
// Country flag
|
if (tender.countryCode != null)
|
||||||
SizedBox(
|
Flag(countryCode: tender.countryCode!),
|
||||||
width: 32.0.w(),
|
|
||||||
height: 21.0.h(),
|
|
||||||
child: CountryFlag.fromCountryCode(
|
|
||||||
tender.countryCode!,
|
|
||||||
shape: RoundedRectangle(4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
+1
-1
@@ -23,10 +23,10 @@ dependencies:
|
|||||||
json_annotation: ^4.8.1
|
json_annotation: ^4.8.1
|
||||||
http: ^1.5.0
|
http: ^1.5.0
|
||||||
intl: ^0.20.2
|
intl: ^0.20.2
|
||||||
country_flags: ^3.3.0
|
|
||||||
flutter_launcher_icons: ^0.14.4
|
flutter_launcher_icons: ^0.14.4
|
||||||
toastification: ^3.0.3
|
toastification: ^3.0.3
|
||||||
pinput: ^5.0.2
|
pinput: ^5.0.2
|
||||||
|
path_provider: ^2.1.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user