download some codes

This commit is contained in:
DASHU
2025-05-08 14:00:21 +08:00
parent 586e76df5c
commit a55faa3f1e
19 changed files with 702 additions and 19 deletions

View File

@@ -0,0 +1,182 @@
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:nowser/const/base.dart';
import 'package:nowser/data/download_log.dart';
import 'package:nowser/main.dart';
import 'package:nowser/util/file_size_util.dart';
import 'package:path/path.dart' as path;
class DownloadListItemComponent extends StatelessWidget {
DownloadLog downloadLog;
DownloadListItemComponent(this.downloadLog);
@override
Widget build(BuildContext context) {
var themeData = Theme.of(context);
Widget fileIconWidget = const Icon(
AntDesign.file1,
size: 40,
);
if (downloadLog.fileName != null) {
var mimeType = getFileType(downloadLog.fileName!);
if (mimeType.contains("image")) {
fileIconWidget = const Icon(
Icons.image,
size: 40,
);
} else if (mimeType.contains("markdown")) {
fileIconWidget = const Icon(
AntDesign.file_markdown,
size: 40,
);
} else if (mimeType.contains("document")) {
fileIconWidget = const Icon(
AntDesign.filetext1,
size: 40,
);
} else if (mimeType.contains("video")) {
fileIconWidget = const Icon(
Icons.movie,
size: 40,
);
} else if (mimeType.contains("audio")) {
fileIconWidget = const Icon(
Icons.music_note,
size: 40,
);
} else if (mimeType.contains("archive")) {
fileIconWidget = const Icon(
Icons.folder_zip,
size: 40,
);
} else if (mimeType.contains("apk")) {
fileIconWidget = const Icon(
AntDesign.android,
size: 40,
);
}
}
Widget rightIcon = GestureDetector(
onTap: () {},
child: const Icon(
Icons.more_horiz,
),
);
if (downloadLog.progress != null) {
rightIcon = GestureDetector(
onTap: () {},
child: const Icon(
Icons.stop_circle_outlined,
),
);
}
Widget fileStatusWidget = Container();
if (downloadLog.progress != null) {
fileStatusWidget = Text(
"${(downloadLog.progress! * 100).toStringAsFixed(1)}%",
style: TextStyle(
fontSize: themeData.textTheme.bodySmall!.fontSize,
color: themeData.hintColor,
),
);
} else if (downloadLog.fileSize != null) {
fileStatusWidget = Text(
FileSizeUtil.getFileSize(downloadLog.fileSize!),
style: TextStyle(
fontSize: themeData.textTheme.bodySmall!.fontSize,
color: themeData.hintColor,
),
);
}
return Container(
padding: const EdgeInsets.only(
left: Base.BASE_PADDING_HALF,
right: Base.BASE_PADDING_HALF,
top: 2,
bottom: 2,
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(6),
child: fileIconWidget,
),
Expanded(
child: Container(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
downloadLog.fileName ?? "unknow_file",
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
fileStatusWidget,
],
),
),
),
Container(
padding: const EdgeInsets.all(6),
child: rightIcon,
),
],
),
);
}
String getFileType(String filePath) {
// 获取扩展名(含点,如 '.jpg'
String extension = path.extension(filePath).toLowerCase();
// 处理无扩展名的情况
if (extension.isEmpty) return 'other';
// 去掉点并检查映射
String cleanedExtension =
extension.startsWith('.') ? extension.substring(1) : extension;
return _fileExtensionToType[cleanedExtension] ?? 'other';
}
Map<String, String> _fileExtensionToType = {
// 图片类型
'jpg': 'image',
'jpeg': 'image',
'png': 'image',
'gif': 'image',
'webp': 'image',
'bmp': 'image',
'mp4': 'video',
'mov': 'video',
'avi': 'video',
'mkv': 'video',
'flv': 'video',
'pdf': 'document',
'doc': 'document',
'docx': 'document',
'xls': 'document',
'xlsx': 'document',
'ppt': 'document',
'pptx': 'document',
'txt': 'document',
'md': 'markdown',
'markdown': 'markdown',
'mp3': 'audio',
'wav': 'audio',
'aac': 'audio',
'ogg': 'audio',
'zip': 'archive',
'rar': 'archive',
'7z': 'archive',
'tar': 'archive',
'gz': 'archive',
'apk': 'apk',
};
}

View File

@@ -0,0 +1,121 @@
import 'package:flutter/material.dart';
import 'package:nostr_sdk/utils/platform_util.dart';
import 'package:nowser/provider/download_provider.dart';
import 'package:nowser/util/router_util.dart';
import '../const/base.dart';
import '../generated/l10n.dart';
import '../main.dart';
import '../util/table_mode_util.dart';
class DownloadTaskDialog extends StatefulWidget {
String downloadUrl;
DownloadTaskDialog(this.downloadUrl);
static Future<void> show(BuildContext context, String downloadUrl) async {
await showDialog<String>(
context: context,
builder: (_context) {
return DownloadTaskDialog(downloadUrl);
},
);
}
@override
State<StatefulWidget> createState() {
return _DownloadTaskDialog();
}
}
class _DownloadTaskDialog extends State<DownloadTaskDialog> {
TextEditingController urlTextController = TextEditingController();
TextEditingController fileNameTextController = TextEditingController();
late S s;
@override
void initState() {
super.initState();
String fileName =
widget.downloadUrl.substring(widget.downloadUrl.lastIndexOf('/') + 1);
urlTextController.text = widget.downloadUrl;
fileNameTextController.text = fileName;
}
@override
Widget build(BuildContext context) {
var themeData = Theme.of(context);
s = S.of(context);
List<Widget> list = [];
list.add(Container(
margin: EdgeInsets.only(
bottom: Base.BASE_PADDING,
),
child: Text(
s.Downloads,
style: TextStyle(
fontSize: themeData.textTheme.bodyLarge!.fontSize,
fontWeight: FontWeight.bold,
),
),
));
list.add(Container(
child: TextField(
controller: urlTextController,
decoration: InputDecoration(
labelText: s.Url,
),
),
));
list.add(Container(
child: TextField(
controller: fileNameTextController,
decoration: InputDecoration(
labelText: s.Name,
),
),
));
list.add(Container(
margin: EdgeInsets.only(
top: Base.BASE_PADDING * 2,
bottom: Base.BASE_PADDING,
),
width: double.infinity,
child: FilledButton(onPressed: confirm, child: Text(s.Confirm)),
));
Widget main = Container(
child: Column(
mainAxisSize: MainAxisSize.min,
children: list,
),
);
if (PlatformUtil.isPC() || TableModeUtil.isTableMode()) {
main = Container(
width: mediaDataCache.size.width / 2,
child: main,
);
}
return Dialog(
child: Container(
padding: EdgeInsets.all(Base.BASE_PADDING * 2),
child: main,
),
);
}
void confirm() {
var url = urlTextController.text;
var fileName = fileNameTextController.text;
downloadProvider.startDownload(url, fileName);
RouterUtil.back(context);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:nostr_sdk/utils/string_util.dart';
import 'package:nowser/component/download_task_dialog.dart';
import 'package:nowser/main.dart';
import '../../const/base.dart';
@@ -98,7 +99,9 @@ class _LongPressDialog extends State<LongPressDialog> {
));
list.add(LongPressDialogItem(
s.Download_image,
onTap: () {},
onTap: () {
DownloadTaskDialog.show(context, src);
},
showBottomLine: false,
));
}
@@ -180,10 +183,10 @@ class LongPressDialogItem extends StatelessWidget {
return GestureDetector(
onTap: () {
RouterUtil.back(context);
if (onTap != null) {
onTap!();
}
RouterUtil.back(context);
},
child: Container(
decoration: BoxDecoration(

View File

@@ -7,6 +7,7 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:nostr_sdk/event.dart';
import 'package:nostr_sdk/utils/platform_util.dart';
import 'package:nostr_sdk/utils/string_util.dart';
import 'package:nowser/component/download_task_dialog.dart';
import 'package:nowser/component/webview/long_press_dialog.dart';
import 'package:nowser/component/webview/web_info.dart';
import 'package:nowser/const/app_type.dart';
@@ -224,6 +225,11 @@ class _WebViewComponent extends State<WebViewComponent>
}
}
},
onDownloadStartRequest: (InAppWebViewController controller,
DownloadStartRequest downloadStartRequest) {
String downloadUrl = downloadStartRequest.url.path;
DownloadTaskDialog.show(context, downloadUrl);
},
),
);
}

View File

@@ -12,4 +12,5 @@ class RouterPath {
static const String AUTH_LOGS = "/authLogs";
static const String SETTING = "/setting";
static const String ABOUT_ME = "/aboutMe";
static const String DOWNLOADS = "/downloads";
}

View File

@@ -29,14 +29,18 @@ class BrowserHistoryDB {
}
static Future<void> deleteByIds(List<int> ids, {DatabaseExecutor? db}) async {
var sql = "delete from browser_history where id in(";
for (var id in ids) {
sql += "?,";
await DB.deleteByIds("browser_history", ids, db: db);
}
sql = sql.substring(0, sql.length - 1);
sql += ")";
db = await DB.getDB(db);
await db.execute(sql, ids);
}
// static Future<void> deleteByIds(List<int> ids, {DatabaseExecutor? db}) async {
// var sql = "delete from browser_history where id in(";
// for (var id in ids) {
// sql += "?,";
// }
// sql = sql.substring(0, sql.length - 1);
// sql += ")";
// db = await DB.getDB(db);
// await db.execute(sql, ids);
// }
}

View File

@@ -2,7 +2,7 @@ import 'package:nostr_sdk/utils/db_util.dart';
import 'package:sqflite/sqflite.dart';
class DB {
static const _VERSION = 2;
static const _VERSION = 3;
static const _dbName = "nowser.db";
@@ -41,14 +41,22 @@ class DB {
"create table bookmark(id integer not null constraint bookmark_pk primary key autoincrement,title text,url text not null,favicon text,weight integer,added_to_index integer, added_to_qa integer,created_at integer);");
db.execute(
"create table browser_history(id integer not null constraint browser_history_pk primary key autoincrement,title text,url text not null,favicon text,created_at integer);");
db.execute(
"create table download_log(id integer constraint download_log_pk primary key autoincrement,url text,file_path text,file_name TEXT,file_size integer,created_at integer);");
}
static Future<void> _onUpgrade(
Database db, int oldVersion, int newVersion) async {
if (oldVersion == 1 && newVersion == 2) {
if (oldVersion == 1) {
db.execute(
"alter table bookmark add added_to_qa integer after added_to_index");
}
if (oldVersion <= 2) {
db.execute(
"create table download_log(id integer constraint download_log_pk primary key autoincrement,url text,file_path text,file_name TEXT,file_size integer,created_at integer);");
}
}
static Future<Database> getCurrentDatabase() async {
@@ -69,4 +77,17 @@ class DB {
_database?.close();
_database = null;
}
static Future<void> deleteByIds(String tableName, List<int> ids,
{DatabaseExecutor? db}) async {
var sql = "delete from $tableName where id in(";
for (var id in ids) {
sql += "?,";
}
sql = sql.substring(0, sql.length - 1);
sql += ")";
db = await DB.getDB(db);
await db.execute(sql, ids);
}
}

View File

@@ -0,0 +1,48 @@
class DownloadLog {
int? id;
String? url;
String? filePath;
String? fileName;
int? fileSize;
int? createdAt;
String? taskId;
double? progress;
DownloadLog({
this.id,
this.url,
this.filePath,
this.fileName,
this.fileSize,
this.createdAt,
this.taskId,
this.progress,
});
DownloadLog.fromJson(Map<String, dynamic> json) {
id = json['id'];
url = json['url'];
filePath = json['file_path'];
fileName = json['file_name'];
fileSize = json['file_size'];
createdAt = json['created_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['url'] = url;
data['file_path'] = filePath;
data['file_name'] = fileName;
data['file_size'] = fileSize;
data['created_at'] = createdAt;
return data;
}
}

View File

@@ -0,0 +1,34 @@
import 'package:nowser/data/download_log.dart';
import 'package:sqflite/sqflite.dart';
import 'db.dart';
class DownloadLogDB {
static Future<int?> total({DatabaseExecutor? db}) async {
db = await DB.getDB(db);
var sql = "select count(1) from download_log";
return Sqflite.firstIntValue(await db.rawQuery(sql));
}
static Future<int> insert(DownloadLog o, {DatabaseExecutor? db}) async {
db = await DB.getDB(db);
return await db.insert("download_log", o.toJson());
}
static Future<List<DownloadLog>> all({DatabaseExecutor? db}) async {
List<DownloadLog> objs = [];
List<Object?>? arguments = [];
db = await DB.getDB(db);
var sql = "select * from download_log order by created_at desc";
List<Map<String, dynamic>> list = await db.rawQuery(sql, arguments);
for (var i = 0; i < list.length; i++) {
var json = list[i];
objs.add(DownloadLog.fromJson(json));
}
return objs;
}
static Future<void> deleteByIds(List<int> ids, {DatabaseExecutor? db}) async {
await DB.deleteByIds("download_log", ids, db: db);
}
}

View File

@@ -15,6 +15,7 @@ import 'package:nowser/provider/android_signer_mixin.dart';
import 'package:nowser/provider/app_provider.dart';
import 'package:nowser/provider/bookmark_provider.dart';
import 'package:nowser/provider/build_in_relay_provider.dart';
import 'package:nowser/provider/download_provider.dart';
import 'package:nowser/provider/key_provider.dart';
import 'package:nowser/provider/permission_check_mixin.dart';
import 'package:nowser/provider/web_provider.dart';
@@ -24,6 +25,7 @@ import 'package:nowser/router/apps/add_remote_app_router.dart';
import 'package:nowser/router/apps/apps_router.dart';
import 'package:nowser/router/auth_log/auth_logs_router.dart';
import 'package:nowser/router/bookmark/bookmark_router.dart';
import 'package:nowser/router/downloads/downloads_router.dart';
import 'package:nowser/router/history/history_router.dart';
import 'package:nowser/router/index/index_router.dart';
import 'package:nowser/router/keys/keys_router.dart';
@@ -65,6 +67,8 @@ late RootIsolateToken rootIsolateToken;
late BuildInRelayProvider buildInRelayProvider;
late DownloadProvider downloadProvider;
const QuickActions quickActions = QuickActions();
BookmarkProvider bookmarkProvider = BookmarkProvider();
@@ -135,6 +139,7 @@ Future<void> doInit() async {
settingProvider = futureResultList[0] as SettingProvider;
webProvider = WebProvider();
remoteSigningProvider = RemoteSigningProvider();
downloadProvider = DownloadProvider();
}
class MyApp extends StatefulWidget {
@@ -197,6 +202,7 @@ class _MyApp extends State<MyApp> {
RouterPath.AUTH_LOGS: (context) => AuthLogsRouter(),
RouterPath.SETTING: (context) => SettingRouter(indexReload: reload),
RouterPath.ABOUT_ME: (context) => AboutMeRouter(),
RouterPath.DOWNLOADS: (context) => DownloadsRouter(),
};
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
@@ -229,6 +235,9 @@ class _MyApp extends State<MyApp> {
ListenableProvider<BookmarkProvider>.value(
value: bookmarkProvider,
),
ListenableProvider<DownloadProvider>.value(
value: downloadProvider,
),
],
child: MaterialApp(
builder: BotToastInit(),

View File

@@ -0,0 +1,100 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:nowser/data/download_log_db.dart';
import 'package:path_provider/path_provider.dart';
import 'package:background_downloader/background_downloader.dart';
import '../data/download_log.dart';
class DownloadProvider extends ChangeNotifier {
FileDownloader fileDownloader = FileDownloader();
List<DownloadLog> currentDownloadLogs = [];
Map<String, DownloadTask> taskMap = {};
// Future<void> init() async {
// await _reloadData();
// }
// Future<void> _reloadData() async {
// // _downloadLogs = await BookmarkDB.all();
// }
// Future<void> reload() async {
// await _reloadData();
// notifyListeners();
// }
void pauseDownload(String taskId) {
fileDownloader.database.allRecords();
var downloadTask = taskMap[taskId];
if (downloadTask != null) {
fileDownloader.pause(downloadTask);
}
}
void resumeDownload(String taskId) {
var downloadTask = taskMap[taskId];
if (downloadTask != null) {
fileDownloader.resume(downloadTask);
}
}
Future<void> startDownload(String url, String fileName) async {
var downloadDir = await getApplicationDocumentsDirectory();
var downloadDirPath = downloadDir.absolute.path;
print("url $url fileName $fileName downloadDirPath $downloadDirPath");
final task = DownloadTask(
url: url,
filename: fileName,
baseDirectory: BaseDirectory.applicationDocuments,
directory: 'downloads',
updates: Updates.statusAndProgress, // request status and progress updates
requiresWiFi: false,
retries: 5,
allowPause: true,
);
var downloadLog = DownloadLog(
url: url,
filePath:
"$downloadDirPath${Platform.pathSeparator}downloads${Platform.pathSeparator}$fileName",
fileName: fileName,
taskId: task.taskId,
progress: 0,
);
currentDownloadLogs.add(downloadLog);
taskMap[task.taskId] = task;
// Start download, and wait for result. Show progress and status changes
// while downloading
var downloadResult = await fileDownloader.download(
task,
onProgress: (progress) {
print('Progress: ${progress * 100}%');
downloadLog.progress = progress;
notifyListeners();
},
onStatus: (status) {
print('Status: $status');
},
);
if (downloadResult.status == TaskStatus.complete) {
var file = File(downloadLog.filePath!);
downloadLog.fileSize = file.lengthSync();
downloadLog.createdAt = DateTime.now().millisecondsSinceEpoch ~/ 1000;
DownloadLogDB.insert(downloadLog);
}
currentDownloadLogs.remove(downloadLog);
notifyListeners();
}
}

View File

@@ -105,6 +105,10 @@ class _BookmarkRouter extends CustState<BookmarkRouter>
setState(() {
selectedIds.add(bookmark.id!);
});
} else {
setState(() {
selectedIds.remove(bookmark.id!);
});
}
});

View File

@@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:nowser/component/cust_state.dart';
import 'package:nowser/component/download_list_item_component.dart';
import 'package:nowser/data/download_log.dart';
import 'package:nowser/data/download_log_db.dart';
import 'package:nowser/provider/download_provider.dart';
import 'package:provider/provider.dart';
import '../../component/appbar_back_btn_component.dart';
import '../../component/deletable_list_mixin.dart';
import '../../const/base.dart';
import '../../generated/l10n.dart';
import '../../util/router_util.dart';
class DownloadsRouter extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _DownloadsRouter();
}
}
class _DownloadsRouter extends CustState<DownloadsRouter>
with DeletableListMixin {
List<int> selectedIds = [];
List<DownloadLog> completedLogs = [];
@override
Future<void> onReady(BuildContext context) async {
var allList = await DownloadLogDB.all();
print(allList);
setState(() {
completedLogs = allList;
});
}
@override
Widget doBuild(BuildContext context) {
var themeData = Theme.of(context);
var s = S.of(context);
var _downloadProvider = Provider.of<DownloadProvider>(context);
List<DownloadLog> currentDownloadLogs =
_downloadProvider.currentDownloadLogs;
List<Widget> list = [];
for (var logItem in currentDownloadLogs) {
list.add(wrapItem(DownloadListItemComponent(logItem), logItem));
}
int? lastMonth;
int? lastDay;
for (var logItem in completedLogs) {
if (logItem.createdAt != null) {
var date =
DateTime.fromMillisecondsSinceEpoch(logItem.createdAt! * 1000);
if (date.month != lastMonth || date.day != lastDay) {
var dateStr = DateFormat.yMd().format(date);
list.add(Container(
margin: EdgeInsets.only(
left: Base.BASE_PADDING, top: Base.BASE_PADDING),
child: Text(
dateStr,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
));
}
}
list.add(wrapItem(DownloadListItemComponent(logItem), logItem));
}
return Scaffold(
appBar: AppBar(
leading: AppbarBackBtnComponent(),
title: Text(
s.Downloads,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: themeData.textTheme.bodyLarge!.fontSize,
),
),
actions: genAppBarActions(context),
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: list,
),
),
);
}
Widget wrapItem(Widget child, DownloadLog downloadLog) {
return wrapListItem(child, onTap: () {
// RouterUtil.back(context, history.url);
}, onSelect: () {
if (!selectedIds.contains(downloadLog.id)) {
setState(() {
selectedIds.add(downloadLog.id!);
});
} else {
setState(() {
selectedIds.remove(downloadLog.id!);
});
}
});
}
}

View File

@@ -86,6 +86,10 @@ class _HistoryRouter extends CustState<HistoryRouter> with DeletableListMixin {
setState(() {
selectedIds.add(history.id!);
});
} else {
setState(() {
selectedIds.remove(history.id!);
});
}
});

View File

@@ -155,7 +155,7 @@ class _WebControlComponent extends State<WebControlComponent> {
size: 30,
),
onTap: () {
BotToast.showText(text: s.Comming_soon);
RouterUtil.router(context, RouterPath.DOWNLOADS);
},
),
),

View File

@@ -9,6 +9,7 @@ import 'package:nowser/const/router_path.dart';
import 'package:nowser/data/auth_log_db.dart';
import 'package:nowser/data/bookmark_db.dart';
import 'package:nowser/data/browser_history_db.dart';
import 'package:nowser/data/download_log_db.dart';
import 'package:nowser/main.dart';
import 'package:nowser/provider/app_provider.dart';
import 'package:nowser/provider/key_provider.dart';
@@ -53,6 +54,7 @@ class _MeRouter extends CustState<MeRouter> {
Future<void> updateNumber() async {
bookmarkNum = await BookmarkDB.total();
historyNum = await BrowserHistoryDB.total();
downloadNum = await DownloadLogDB.total();
setState(() {});
}
@@ -186,7 +188,7 @@ class _MeRouter extends CustState<MeRouter> {
name: s.Downloads,
iconData: Icons.download,
onTap: () {
BotToast.showText(text: s.Comming_soon);
RouterUtil.router(context, RouterPath.DOWNLOADS);
},
));
// webItemList.add(MeRouterWebItemComponent(

View File

@@ -0,0 +1,12 @@
class FileSizeUtil {
static String getFileSize(int fileSize) {
if (fileSize > 1024 * 1024 * 1024) {
return "${fileSize ~/ (1024 * 1024 * 1024)} GB";
} else if (fileSize > 1024 * 1024) {
return "${fileSize ~/ (1024 * 1024)} MB";
} else if (fileSize > 1024) {
return "${fileSize ~/ 1024} KB";
}
return "$fileSize B";
}
}

View File

@@ -33,6 +33,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
background_downloader:
dependency: "direct main"
description:
name: background_downloader
sha256: "3a796f29f0834b3e5698e7ac2a39e04939734395420349ff22e1ac2c73a0f73d"
url: "https://pub.dev"
source: hosted
version: "9.2.1"
base58check:
dependency: transitive
description:
@@ -580,6 +588,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
@@ -605,7 +621,7 @@ packages:
source: hosted
version: "1.15.0"
mime:
dependency: transitive
dependency: "direct main"
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
@@ -651,13 +667,13 @@ packages:
source: hosted
version: "1.9.0"
path_provider:
dependency: transitive
dependency: "direct main"
description:
name: path_provider
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.1.5"
path_provider_android:
dependency: transitive
description:

View File

@@ -64,6 +64,9 @@ dependencies:
searchfield: ^1.2.7
# webview_cef: ^0.2.2
flutter_font_icons: ^2.2.7
path_provider: ^2.1.5
background_downloader: ^9.2.1
mime: ^2.0.0
dev_dependencies:
flutter_launcher_icons: ^0.13.1