diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index dbff0b0..eb0f32c 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -7,7 +7,9 @@
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
-
+
diff --git a/lib/classes/utils.dart b/lib/classes/utils.dart
index 8a01de6..194da3b 100644
--- a/lib/classes/utils.dart
+++ b/lib/classes/utils.dart
@@ -186,4 +186,48 @@ class Utils {
await file.copy(storagePath);
Utils.showToast("تم التنزيل");
}
+
+ static void downloadFileAboveAndroid32(String url, String path, {Function(List bytes) onResponse}) {
+ print("url:$url");
+ print("path:$path");
+ var httpClient = http.Client();
+ var request = http.Request('GET', Uri.parse(url));
+ var response = httpClient.send(request);
+ String dir = path;
+
+ List> chunks = [];
+ int downloaded = 0;
+ int lastVal;
+
+ response.asStream().listen((http.StreamedResponse r) {
+ r.stream.listen((List chunk) {
+ debugPrint('downloadPercentage: ${downloaded / r.contentLength * 100}');
+ lastVal = (downloaded / r.contentLength * 100).toInt();
+
+ chunks.add(chunk);
+ downloaded += chunk.length;
+ }, onDone: () async {
+ debugPrint('downloadPercentage: ${downloaded / r.contentLength * 100}');
+ if (lastVal == 0) {
+ onResponse([]);
+ return;
+ }
+ final Uint8List bytes = Uint8List(r.contentLength);
+ int offset = 0;
+ for (List chunk in chunks) {
+ bytes.setRange(offset, offset + chunk.length, chunk);
+ offset += chunk.length;
+ }
+ onResponse(bytes);
+ return;
+ }, onError: (ex) {
+ debugPrint("onError:$ex");
+ onResponse([]);
+ }, cancelOnError: true);
+ }).onError((ex) {
+ debugPrint("onError:$ex");
+ onResponse([]);
+ });
+ }
+
}
diff --git a/lib/ui/screens/pdf_viewer_screen.dart b/lib/ui/screens/pdf_viewer_screen.dart
index 844d30b..d47d730 100644
--- a/lib/ui/screens/pdf_viewer_screen.dart
+++ b/lib/ui/screens/pdf_viewer_screen.dart
@@ -2,11 +2,13 @@ import 'dart:async';
import 'dart:io';
import 'dart:ui';
+import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
+import 'package:shared_storage/shared_storage.dart';
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
import 'package:tangheem/api/tangheem_user_api_client.dart';
import 'package:tangheem/app_state/app_state.dart';
@@ -121,10 +123,14 @@ class _PdfListScreenState extends State {
width: 44,
height: 50,
).onPress(() async {
- if (await _requestStoragePermission()) {
- startFileDownload(contentList[index].exposeFilePath, contentList[index].fileName);
+ if (Platform.isAndroid && (await DeviceInfoPlugin().androidInfo).version.sdkInt > 29) {
+ savePdfToAboveAndroid32(contentList[index].exposeFilePath, contentList[index].fileName);
} else {
- Utils.showToast("يجب أن تعطي الإذن للتنزيل.");
+ if (await _requestStoragePermission()) {
+ startFileDownload(contentList[index].exposeFilePath, contentList[index].fileName);
+ } else {
+ Utils.showToast("يجب أن تعطي الإذن للتنزيل.");
+ }
}
}),
7.width,
@@ -181,6 +187,51 @@ class _PdfListScreenState extends State {
),
);
}
+
+ void savePdfToAboveAndroid32(String pathUrl, String fileName) async {
+ List _persistedPermissionUris = await persistedUriPermissions();
+ if (_persistedPermissionUris.isNotEmpty) {
+ UriPermission permissionUri = _persistedPermissionUris.first;
+ startFileDownloadAboveAndroid32(pathUrl, fileName, permissionUri.uri);
+ } else {
+ Uri selectedDocumentUris = await openDocumentTree();
+ if (selectedDocumentUris == null) return;
+ savePdfToAboveAndroid32(pathUrl, fileName);
+ }
+ }
+
+ void startFileDownloadAboveAndroid32(String url, String fileName, Uri persistentUri) async {
+ try {
+ Utils.showLoading(context);
+ //String path = await Utils.getStoragePath(fileName);
+ Utils.downloadFileAboveAndroid32(ApiConsts.baseUrl + url, "path", onResponse: (bytes) async {
+ Utils.hideLoading(context);
+ if (bytes.isNotEmpty) {
+ final documentFile = await persistentUri.toDocumentFile();
+ final child = await documentFile?.child(fileName);
+ if (child == null) {
+ documentFile?.createFileAsBytes(
+ mimeType: 'application/pdf ',
+ bytes: bytes,
+ displayName: fileName,
+ );
+ } else {
+ documentFile?.writeToFileAsBytes(
+ bytes: bytes,
+ mode: FileMode.write,
+ );
+ }
+
+ Utils.showToast("تم حفظ الملف بنجاح");
+ } else {
+ Utils.showToast("فشل حفظ الملف ، حاول مرة أخرى");
+ }
+ });
+ } catch (ex) {
+ print(ex);
+ Utils.hideLoading(context);
+ }
+ }
}
class PdfViewerScreen extends StatelessWidget {
diff --git a/lib/widgets/aya_player_widget.dart b/lib/widgets/aya_player_widget.dart
index 16ede2e..855a05d 100644
--- a/lib/widgets/aya_player_widget.dart
+++ b/lib/widgets/aya_player_widget.dart
@@ -4,6 +4,7 @@ import 'dart:io';
import 'dart:math' as math;
import 'dart:typed_data';
+import 'package:device_info_plus/device_info_plus.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
@@ -12,6 +13,7 @@ import 'package:http/http.dart' as http;
import 'package:just_audio/just_audio.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
+import 'package:shared_storage/shared_storage.dart' as saf;
import 'package:tangheem/classes/colors.dart';
import 'package:tangheem/classes/consts.dart';
import 'package:tangheem/classes/utils.dart';
@@ -185,7 +187,11 @@ class _AyaPlayerWidgetState extends State {
),
if (widget.voiceNoteList?.isNotEmpty ?? false)
commonIconButton("assets/icons/download_aya.svg", () async {
- if (await _requestPermission()) {
+ if (Platform.isAndroid && (await DeviceInfoPlugin().androidInfo).version.sdkInt > 29) {
+ String filePath = widget.voiceNoteList[_player.currentIndex].exposeFilePath;
+ saveAudioToAboveAndroid32(filePath, "Tangheem-${widget.voiceNoteList[_player.currentIndex].userName ?? ""}-${widget.voiceNoteList[_player.currentIndex].fileName}",
+ widget.voiceNoteList[_player.currentIndex].fileType);
+ } else if (await _requestPermission()) {
saveToPhoneStorage(widget.voiceNoteList[_player.currentIndex].exposeFilePath, widget.voiceNoteList[_player.currentIndex].userName ?? "");
} else {
Utils.showToast("يجب اعطاء الاذن لتنزيل الآية");
@@ -393,4 +399,49 @@ class _AyaPlayerWidgetState extends State {
onResponse(false);
});
}
+
+ void saveAudioToAboveAndroid32(String pathUrl, String fileName, String fileType) async {
+ List _persistedPermissionUris = await saf.persistedUriPermissions();
+ if (_persistedPermissionUris.isNotEmpty) {
+ saf.UriPermission permissionUri = _persistedPermissionUris.first;
+ startFileDownloadAboveAndroid32(pathUrl, fileName, permissionUri.uri, fileType);
+ } else {
+ Uri selectedDocumentUris = await saf.openDocumentTree();
+ if (selectedDocumentUris == null) return;
+ saveAudioToAboveAndroid32(pathUrl, fileName, fileType);
+ }
+ }
+
+ void startFileDownloadAboveAndroid32(String url, String fileName, Uri persistentUri, String fileType) async {
+ try {
+ Utils.showLoading(context);
+ //String path = await Utils.getStoragePath(fileName);
+ Utils.downloadFileAboveAndroid32(ApiConsts.baseUrl + url, "path", onResponse: (bytes) async {
+ Utils.hideLoading(context);
+ if (bytes.isNotEmpty) {
+ final documentFile = await persistentUri.toDocumentFile();
+ final child = await documentFile?.child(fileName);
+ if (child == null) {
+ documentFile?.createFileAsBytes(
+ mimeType: fileType,
+ bytes: bytes,
+ displayName: fileName,
+ );
+ } else {
+ documentFile?.writeToFileAsBytes(
+ bytes: bytes,
+ mode: FileMode.write,
+ );
+ }
+
+ Utils.showToast("تم حفظ الملف بنجاح");
+ } else {
+ Utils.showToast("فشل حفظ الملف ، حاول مرة أخرى");
+ }
+ });
+ } catch (ex) {
+ print(ex);
+ Utils.hideLoading(context);
+ }
+ }
}
diff --git a/lib/widgets/aya_record_widget.dart b/lib/widgets/aya_record_widget.dart
index e3be5b7..41557d5 100644
--- a/lib/widgets/aya_record_widget.dart
+++ b/lib/widgets/aya_record_widget.dart
@@ -1,5 +1,6 @@
import 'dart:io';
+import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:just_audio/just_audio.dart';
@@ -7,9 +8,11 @@ import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:record_mp3/record_mp3.dart';
import 'package:tangheem/classes/colors.dart';
+import 'package:tangheem/classes/consts.dart';
import 'package:tangheem/classes/utils.dart';
import 'package:tangheem/extensions/string_extensions.dart';
import 'package:tangheem/extensions/widget_extensions.dart';
+import 'package:shared_storage/shared_storage.dart' as saf;
class AyaRecordWidget extends StatefulWidget {
AyaRecordWidget({Key key}) : super(key: key);
@@ -219,7 +222,13 @@ class _AyaRecordWidgetState extends State {
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () async {
- if (await _requestStoragePermission()) {
+ if (Platform.isAndroid && (await DeviceInfoPlugin().androidInfo).version.sdkInt > 29) {
+ if (recordFilePath != null && File(recordFilePath).existsSync()) {
+ saveAudioToAboveAndroid32(recordFilePath, "Tangheem${DateTime.now().millisecondsSinceEpoch}.mp3", "audio/mpeg");
+ } else {
+ Utils.showToast("يجب عليك تسجيل صوتك أولا");
+ }
+ } else if (await _requestStoragePermission()) {
if (recordFilePath != null && File(recordFilePath).existsSync()) {
saveToPhoneStorage(recordFilePath);
} else {
@@ -231,7 +240,7 @@ class _AyaRecordWidgetState extends State {
},
child: Container(
height: 31,
- width: 135,
+ width: 135,
padding: EdgeInsets.only(left: 12, right: 12),
alignment: Alignment.center,
decoration: BoxDecoration(
@@ -321,4 +330,41 @@ class _AyaRecordWidgetState extends State {
await file.copy(storagePath);
Utils.showToast("تم التنزيل");
}
+
+ void saveAudioToAboveAndroid32(String pathUrl, String fileName, String fileType) async {
+ List _persistedPermissionUris = await saf.persistedUriPermissions();
+ if (_persistedPermissionUris.isNotEmpty) {
+ saf.UriPermission permissionUri = _persistedPermissionUris.first;
+ startFileAboveAndroid32(pathUrl, fileName, permissionUri.uri, fileType);
+ } else {
+ Uri selectedDocumentUris = await saf.openDocumentTree();
+ if (selectedDocumentUris == null) return;
+ saveAudioToAboveAndroid32(pathUrl, fileName, fileType);
+ }
+ }
+
+ void startFileAboveAndroid32(String filePath, String fileName, Uri persistentUri, String fileType) async {
+ try {
+ File file = File(filePath);
+ final documentFile = await persistentUri.toDocumentFile();
+ final child = await documentFile?.child(fileName);
+ if (child == null) {
+ documentFile?.createFileAsBytes(
+ mimeType: fileType,
+ bytes: file.readAsBytesSync(),
+ displayName: fileName,
+ );
+ } else {
+ documentFile?.writeToFileAsBytes(
+ bytes: file.readAsBytesSync(),
+ mode: FileMode.write,
+ );
+ }
+
+ Utils.showToast("تم حفظ الملف بنجاح");
+ } catch (ex) {
+ print(ex);
+ Utils.hideLoading(context);
+ }
+ }
}
diff --git a/pubspec.yaml b/pubspec.yaml
index ce3409e..062b11e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -54,6 +54,9 @@ dependencies:
flutter_html: ^2.2.1
flutter_inappwebview: ^5.7.2+3
dotted_border: ^2.0.0+1
+ device_info_plus: ^6.0.0
+ shared_storage: ^0.7.1
+
dev_dependencies:
flutter_test:
sdk: flutter