downloadAllFontsZipFile method
Downloads a zip file containing all fonts for the specified font index.
This method asynchronously downloads a zip file that contains all the fonts
associated with the given fontIndex. The downloaded file will be saved
locally for later use.
fontIndex - The index of the font set to be downloaded.
Returns a Future that completes when the download is finished.
Implementation
Future<void> downloadAllFontsZipFile(int fontIndex) async {
// على الويب لا نحتاج التنزيل، سنحمّل مباشرة عند الحاجة
if (kIsWeb) {
state.isFontDownloaded.value = true;
state.isDownloadingFonts.value = false;
state.isPreparingDownload.value = false;
update(['fontsDownloadingProgress']);
return;
}
// if (GetStorage().read(StorageConstants().isDownloadedCodeV2Fonts) ??
// false || state.isDownloadingFonts.value) {
// return Future.value();
// }
try {
state.isPreparingDownload.value = true;
state.isDownloadingFonts.value = true;
update(['fontsDownloadingProgress']);
// قائمة بالروابط البديلة للتحميل
final urls = (!kIsWeb &&
(Platform.isAndroid || Platform.isIOS || Platform.isFuchsia))
? [
'https://github.com/alheekmahlib/Islamic_database/releases/download/fonts/qcf4_woff.zip',
// مرايا WOFF
'https://cdn.jsdelivr.net/gh/alheekmahlib/Islamic_database@main/quran_database/Quran%20Font/qcf4_woff.zip',
'https://rawcdn.githack.com/alheekmahlib/Islamic_database/main/quran_database/Quran%20Font/qcf4_woff.zip',
'https://rawgit.flutter-io.cn/alheekmahlib/Islamic_database/main/quran_database/Quran%20Font/qcf4_woff.zip',
'https://github.com/alheekmahlib/Islamic_database/raw/refs/heads/main/quran_database/Quran%20Font/qcf4_woff.zip',
]
: [
'https://github.com/alheekmahlib/Islamic_database/releases/download/fonts/qcf4_ttf.zip',
// مرايا TTF
'https://cdn.jsdelivr.net/gh/alheekmahlib/Islamic_database@main/quran_database/Quran%20Font/qcf4_ttf.zip',
'https://rawcdn.githack.com/alheekmahlib/Islamic_database/main/quran_database/Quran%20Font/qcf4_ttf.zip',
'https://rawgit.flutter-io.cn/alheekmahlib/Islamic_database/main/quran_database/Quran%20Font/qcf4_ttf.zip',
'https://github.com/alheekmahlib/Islamic_database/raw/refs/heads/main/quran_database/Quran%20Font/qcf4_ttf.zip',
];
// حدد مسار الحفظ
final fontsDir = Directory(
isPhones ? '${_dir.path}/qcf4_woff' : '${_dir.path}/qcf4_ttf');
if (!await fontsDir.exists()) {
await fontsDir.create(recursive: true);
}
final zipFile = File(isPhones
? '${_dir.path}/qcf4_woff.zip'
: '${_dir.path}/qcf4_ttf.zip');
// حد أدنى للحجم لمنع ملفات HTML/أخطاء CDN المقنّعة
const int minZipSizeBytes = 1024 * 1024; // ~1MB
final dio = Dio()
..options.connectTimeout = const Duration(seconds: 20)
..options.receiveTimeout = const Duration(minutes: 2);
bool extractionSucceeded = false;
for (final url in urls) {
try {
log('Attempting to download from: $url', name: 'FontsDownload');
final response = await dio.get(url,
options: Options(
responseType: ResponseType.stream,
followRedirects: true,
sendTimeout: const Duration(seconds: 30),
headers: {
'User-Agent': 'Flutter/Quran-Library',
'Accept': '*/*',
'Accept-Encoding': 'identity',
},
));
if (response.statusCode != 200) {
log('Failed to connect to $url: ${response.statusCode}',
name: 'FontsDownload');
continue;
}
final contentType =
response.headers.value(Headers.contentTypeHeader) ?? '';
final headerLenStr =
response.headers.value(Headers.contentLengthHeader);
final headerLen = int.tryParse(headerLenStr ?? '0') ?? 0;
if (contentType.startsWith('text/') || contentType.contains('html')) {
log('Rejected $url due to suspicious content-type: $contentType',
name: 'FontsDownload');
continue;
}
if (headerLen > 0 && headerLen < minZipSizeBytes) {
log('Rejected $url due to too small content-length: $headerLen',
name: 'FontsDownload');
continue;
}
// نزّل إلى الملف
final sink = zipFile.openWrite();
int downloaded = 0;
final completer = Completer<void>();
(response.data as ResponseBody).stream.listen(
(chunk) {
downloaded += chunk.length;
sink.add(chunk);
state.isDownloadingFonts.value = true;
state.isPreparingDownload.value = false;
if (headerLen > 0) {
state.fontsDownloadProgress.value =
downloaded / headerLen * 100;
update(['fontsDownloadingProgress']);
}
},
onDone: () async {
await sink.flush();
await sink.close();
completer.complete();
},
onError: (e) async {
await sink.close();
completer.completeError(e);
},
cancelOnError: true,
);
await completer.future;
final size = await zipFile.length();
log('Downloaded ZIP file size: $size bytes');
if (size < minZipSizeBytes) {
log('Zip too small, trying next mirror...', name: 'FontsDownload');
try {
await zipFile.delete();
} catch (_) {}
continue;
}
try {
final bytes = await zipFile.readAsBytes();
final archive = ZipDecoder().decodeBytes(bytes);
if (archive.isEmpty) {
throw const FormatException(
'Failed to extract ZIP file: Archive is empty');
}
for (final file in archive) {
final filename = '${fontsDir.path}/${file.name}';
if (file.isFile) {
final out = File(filename);
await out.create(recursive: true);
await out.writeAsBytes(file.content as List<int>);
}
}
extractionSucceeded = true;
break;
} catch (e) {
log('Failed to extract ZIP from $url: $e', name: 'FontsDownload');
try {
await zipFile.delete();
} catch (_) {}
continue;
}
} catch (e) {
log('Download error with URL, trying next: $e',
name: 'FontsDownload');
try {
if (await zipFile.exists()) await zipFile.delete();
} catch (_) {}
continue;
}
}
if (!extractionSucceeded) {
throw Exception(
'All mirrors failed to provide a valid ZIP or extraction failed');
}
// حفظ حالة التحميل
GetStorage().write(_StorageConstants().isDownloadedCodeV4Fonts, true);
state.fontsDownloadedList.add(fontIndex);
GetStorage().write(
_StorageConstants().fontsDownloadedList, state.fontsDownloadedList);
state.isFontDownloaded.value = true;
state.isDownloadingFonts.value = false;
state.isPreparingDownload.value = false;
state.fontsDownloadProgress.value = 100.0;
update(['fontsDownloadingProgress']);
Get.forceAppUpdate();
log('Fonts unzipped successfully', name: 'FontsDownload');
} catch (e) {
log('Failed to Download Code_v4 fonts: $e', name: 'FontsDownload');
state.isDownloadingFonts.value = false;
state.isPreparingDownload.value = false;
state.fontsDownloadProgress.value = 0.0;
update(['fontsDownloadingProgress']);
throw Exception('Download failed: $e');
}
}