file_manager.dart 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. import 'dart:io';
  2. import 'dart:ui';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/services.dart';
  6. import 'package:flutter/widgets.dart';
  7. import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
  8. import 'package:intl/intl.dart';
  9. import 'package:neofilemanager/Item/Preview/FilePre.dart';
  10. import 'package:neofilemanager/Item/Preview/Impl/ProxyFilePre.dart';
  11. import 'package:neofilemanager/Item/Preview/Impl/TxtFilePre.dart';
  12. import 'package:neofilemanager/OperationButtom/CutOperationButton.dart';
  13. import 'package:neofilemanager/OperationButtom/DeleteOperationButton.dart';
  14. import 'package:neofilemanager/Opertaion/Operation.dart';
  15. import 'package:open_file/open_file.dart';
  16. import 'package:path/path.dart' as p;
  17. import 'Item/Common.dart';
  18. import 'Item/Mode.dart';
  19. import 'Item/widgetItem.dart';
  20. import 'OperationButtom/AddToFavOperationButton.dart';
  21. import 'OperationButtom/CopyOperationButton.dart';
  22. import 'OperationButtom/RemoveFavOperationButton.dart';
  23. import 'OperationButtom/RenameOperationButton.dart';
  24. class MyHome extends StatefulWidget {
  25. const MyHome({Key? key, required this.title}) : super(key: key);
  26. final String title;
  27. @override
  28. _MyHomePageState createState() => _MyHomePageState();
  29. }
  30. class _MyHomePageState extends State<MyHome> {
  31. ValueNotifier<bool> uiShouldChange = ValueNotifier<bool>(false);
  32. List<FileSystemEntity> leftFiles = []; //左列的文件
  33. List<FileSystemEntity> rightFiles = []; //右列的文件
  34. Mode mode = new Mode(false, false, null, null);
  35. int cardColor = 0x22ffffff;
  36. double iconHeight = 30.0;
  37. double iconWidth = 30.0;
  38. double fileFontSize = 9.0;
  39. double subTitleFontSize = 6.0;
  40. ScrollController controller = ScrollController();
  41. List<double> position = [];
  42. Common common = new Common();
  43. ///初始化: 拿到根路径 刷新目录
  44. @override
  45. void initState() {
  46. super.initState();
  47. mode.parentDir = Directory(common.sDCardDir); //获取根路径
  48. new Operation(leftFiles, rightFiles, context,
  49. uiShouldChange: uiShouldChange, mode: mode)
  50. .initPathFiles(common.sDCardDir, -2);
  51. }
  52. @override
  53. Widget build(BuildContext context) {
  54. return ValueListenableBuilder(
  55. valueListenable: uiShouldChange,
  56. builder: (BuildContext context, bool value, Widget child) {
  57. return WillPopScope(
  58. onWillPop: onWillPop,
  59. child: Scaffold(
  60. backgroundColor: Colors.black,
  61. appBar: AppBar(
  62. title: Text(
  63. mode.parentDir?.path == common.sDCardDir
  64. ? 'SD card'
  65. : p.basename(mode.parentDir.path),
  66. ),
  67. centerTitle: true,
  68. elevation: 0.0,
  69. //浮起来的高度
  70. leading: mode.parentDir?.path == common.sDCardDir
  71. ? Container()
  72. : IconButton(
  73. icon: Icon(
  74. Icons.arrow_left,
  75. ),
  76. onPressed: onWillPop,
  77. ), //再标题前面显示的一个控件
  78. ),
  79. body: returnBody(),
  80. bottomNavigationBar: returnBottomBar(),
  81. ),
  82. );
  83. },
  84. );
  85. }
  86. Widget returnBody() {
  87. return Row(
  88. children: <Widget>[
  89. Expanded(
  90. child: _fileListView(leftFiles, -1),
  91. ),
  92. Expanded(
  93. child: _fileListView(rightFiles, 1),
  94. ),
  95. ],
  96. );
  97. }
  98. Widget returnBottomBar() {
  99. return Container(
  100. color: Color(0x44000000),
  101. child: Row(
  102. crossAxisAlignment: CrossAxisAlignment.center,
  103. mainAxisAlignment: MainAxisAlignment.center,
  104. children: <Widget>[
  105. Expanded(
  106. child: CupertinoButton(
  107. child: Icon(Icons.add),
  108. onPressed: () {
  109. moreAction(-1);
  110. },
  111. ),
  112. ),
  113. Expanded(
  114. child: CupertinoButton(
  115. child: Icon(Icons.home),
  116. onPressed: () {
  117. new Operation(leftFiles, rightFiles, context,
  118. uiShouldChange: uiShouldChange, mode: mode)
  119. .initPathFiles(common.sDCardDir, -2);
  120. },
  121. ),
  122. ),
  123. Expanded(
  124. child: CupertinoButton(
  125. child: Icon(Icons.favorite),
  126. onPressed: () {
  127. listFavorite();
  128. },
  129. ),
  130. ),
  131. Expanded(
  132. child: CupertinoButton(
  133. child: Icon(Icons.add),
  134. onPressed: () {
  135. moreAction(1);
  136. },
  137. ),
  138. ),
  139. ],
  140. ),
  141. );
  142. }
  143. ///用于导航返回拦截器的onWillPop
  144. Future<bool> onWillPop() async {
  145. if (mode.parentDir.parent.path != common.sDCardDir &&
  146. mode.parentDir.path != common.sDCardDir) {
  147. /// 如果不是根目录,就跳转到上层目录并刷新目录
  148. // initPathFiles(parentDir.path, 2);
  149. new Operation(leftFiles, rightFiles, context,
  150. uiShouldChange: uiShouldChange, mode: mode)
  151. .initPathFiles(mode.parentDir.path, 2);
  152. } else {
  153. ///否则退出
  154. ///https://blog.csdn.net/weixin_33979203/article/details/88019065
  155. ///Router: 路由 对屏幕界面的抽象 每一个页面都有相应的Page
  156. print('退出时parentDir为:' + mode.parentDir.path);
  157. SystemNavigator.pop();
  158. }
  159. return false;
  160. }
  161. Widget _fileListView(List<FileSystemEntity> files, int type) {
  162. if (files.length == 0)
  163. return Center(child: Text('The folder is empty'));
  164. else {
  165. return Scrollbar(
  166. child: ListView.builder(
  167. physics: BouncingScrollPhysics(),
  168. //控制列表的滚动的发生方式(再列表结束时退回列表,iOS中有类似效果)
  169. controller: controller,
  170. itemCount: files.length,
  171. itemBuilder: (BuildContext context, int index) {
  172. if (FileSystemEntity.isFileSync(files[index].path))
  173. return _buildFileItem(files[index], type, index);
  174. else
  175. return _buildFolderItem(files[index], type, index);
  176. }),
  177. );
  178. }
  179. }
  180. /// 创建文件的ListTile
  181. Widget _buildFileItem(FileSystemEntity file, int type, int index) {
  182. //获取文件最后改动日期
  183. String modifiledTime = DateFormat('yyyy-MM-dd HH:mm:ss', 'zh_CN')
  184. .format(file.statSync().modified.toLocal());
  185. FilePre fileIcon;
  186. fileIcon = ProxyFilePre(
  187. common,
  188. file,
  189. p.extension(file.path),
  190. 50,
  191. 70,
  192. );
  193. Image tempImage;
  194. ///InkWell: 水波纹效果
  195. return InkWell(
  196. child: Container(
  197. decoration: BoxDecoration(
  198. color: Colors.black,
  199. border: Border(bottom: BorderSide(width: 0.5, color: Colors.black12)),
  200. ),
  201. child: ListTile(
  202. leading: FutureBuilder(
  203. future: (file,tempImage)async{
  204. return tempImage;
  205. },
  206. builder: (context,snapshot){
  207. if(snapshot.connectionState==ConnectionState.done){
  208. }
  209. }
  210. ),
  211. title: Text(file.path.substring(file.parent.path.length + 1),
  212. style: TextStyle(fontSize: fileFontSize)),
  213. ///从文件路径中截取文件名字 (file.parent.length+1-文件父目录长度)
  214. subtitle: Text(
  215. '$modifiledTime ${common.getFileSize(file.statSync().size)}',
  216. style: TextStyle(fontSize: subTitleFontSize),
  217. ), //时间和文件大小
  218. ),
  219. ),
  220. onTap: () {
  221. OpenFile.open(file.path);
  222. },
  223. onLongPress: () {
  224. Container fileIconPre;
  225. FilePre filePre;
  226. if (p.extension(file.path) == '.txt') {
  227. filePre = TxtFilePre(filePre);
  228. fileIconPre = Container(
  229. margin: EdgeInsets.all(10),
  230. padding: EdgeInsets.all(10),
  231. decoration: BoxDecoration(
  232. color: Color(0x22ffffff),
  233. borderRadius: BorderRadius.all(Radius.circular(5)),
  234. ),
  235. width: MediaQuery.of(context).size.width / 2,
  236. child: ListView(
  237. children: <Widget>[Text((filePre as TxtFilePre).getContent())],
  238. ),
  239. );
  240. } else {
  241. fileIconPre = Container(
  242. width: MediaQuery.of(context).size.width / 2,
  243. child: Padding(
  244. padding: const EdgeInsets.all(14.0),
  245. child: filePre,
  246. ),
  247. );
  248. }
  249. showModalBottomSheet(
  250. backgroundColor: Color(0x00000000),
  251. context: context,
  252. builder: (BuildContext context) {
  253. return BackdropFilter(
  254. filter: ImageFilter.blur(
  255. sigmaX: 5.0,
  256. sigmaY: 5.0,
  257. ),
  258. child: Row(
  259. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  260. children: <Widget>[
  261. type == -1
  262. ? Container(
  263. width: 0,
  264. )
  265. : fileIconPre,
  266. Column(
  267. mainAxisSize: MainAxisSize.min,
  268. crossAxisAlignment: type == -1
  269. ? CrossAxisAlignment.start
  270. : CrossAxisAlignment.end,
  271. children: <Widget>[
  272. RenameOperationButton(context, file, type, leftFiles,
  273. rightFiles, uiShouldChange, mode)
  274. .returnButton(),
  275. CopyOperationButton(context, file, type, mode)
  276. .returnButton(),
  277. CutOperationButton(context, file, type, mode)
  278. .returnButton(),
  279. AddToOperationButton(context, file, type)
  280. .returnButton(),
  281. RemoveFavOperation(context, file, type)
  282. .returnButton(),
  283. DeleteOperationButton(context, file, type, leftFiles,
  284. rightFiles, mode, uiShouldChange)
  285. .returnButton(),
  286. ],
  287. ),
  288. type == -1
  289. ? fileIconPre
  290. : Container(
  291. width: 0,
  292. ),
  293. ],
  294. ));
  295. });
  296. },
  297. );
  298. }
  299. Future<Widget> getPreview(file) async{
  300. return Image.file(file, height:30, width:40, fit: BoxFit.cover);
  301. }
  302. Widget _buildFolderItem(FileSystemEntity file, int type, int index) {
  303. String modifiledTime = DateFormat('yyyy-MM-dd HH:mm:ss', 'zh_CN')
  304. .format(file.statSync().modified.toLocal());
  305. //InkWell: 水波纹效果
  306. return AnimationConfiguration.staggeredList(
  307. position: index,
  308. duration: const Duration(milliseconds: 100),
  309. child: SlideAnimation(
  310. horizontalOffset: 50,
  311. child: FadeInAnimation(
  312. child: Card(
  313. color: Color(0xff222222),
  314. elevation: 15.0,
  315. margin: EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5),
  316. child: InkWell(
  317. child: Container(
  318. decoration: BoxDecoration(
  319. // color: Colors.black,
  320. // border:
  321. // Border(bottom: BorderSide(width: 0.5, color: Colors.white)),
  322. ),
  323. child: ListTile(
  324. leading: Image.asset(
  325. 'assets/images/folder.png',
  326. height: iconHeight,
  327. width: iconWidth,
  328. ),
  329. title: Row(
  330. children: <Widget>[
  331. Expanded(
  332. child: Text(
  333. file.path.substring(file.parent.path.length + 1),
  334. style: TextStyle(fontSize: fileFontSize)),
  335. ),
  336. ],
  337. ),
  338. subtitle: Text(
  339. modifiledTime,
  340. style: TextStyle(fontSize: subTitleFontSize),
  341. ),
  342. trailing: Icon(
  343. Icons.arrow_right,
  344. size: 12.0,
  345. )
  346. ///trailing: 和leading相对,最后面
  347. ),
  348. ),
  349. onTap: () {
  350. new Operation(leftFiles, rightFiles, context,
  351. uiShouldChange: uiShouldChange, mode: mode)
  352. .initPathFiles(file.path, type);
  353. },
  354. onLongPress: () {
  355. showModalBottomSheet(
  356. backgroundColor: Color(0x00000000),
  357. context: context,
  358. builder: (BuildContext context) {
  359. return BackdropFilter(
  360. filter: ImageFilter.blur(
  361. sigmaX: 5.0,
  362. sigmaY: 5.0,
  363. ),
  364. child: Container(
  365. child: Column(
  366. mainAxisSize: MainAxisSize.min,
  367. crossAxisAlignment: type == -1
  368. ? CrossAxisAlignment.start
  369. : CrossAxisAlignment.end,
  370. children: <Widget>[
  371. new RenameOperationButton(
  372. context,
  373. file,
  374. type,
  375. leftFiles,
  376. rightFiles,
  377. uiShouldChange,
  378. mode)
  379. .returnButton(),
  380. new AddToOperationButton(context, file, type)
  381. .returnButton(),
  382. new RemoveFavOperation(context, file, type)
  383. .returnButton(),
  384. new DeleteOperationButton(
  385. context,
  386. file,
  387. type,
  388. leftFiles,
  389. rightFiles,
  390. mode,
  391. uiShouldChange)
  392. .returnButton(),
  393. ],
  394. ),
  395. ),
  396. );
  397. });
  398. },
  399. ),
  400. ),
  401. ),
  402. ),
  403. );
  404. }
  405. Widget returnFileOperateButton(int cardColor,
  406. Function(FileSystemEntity file) fun, FileSystemEntity file) {
  407. return Container(
  408. width: 170,
  409. child: Card(
  410. color: Color(cardColor),
  411. margin: EdgeInsets.only(left: 5, right: 5, bottom: 20),
  412. child: InkWell(
  413. child: Center(
  414. child: Padding(
  415. padding: EdgeInsets.all(10),
  416. child: Text('收藏', style: TextStyle(color: Colors.cyan)),
  417. ),
  418. ),
  419. onTap: () {
  420. Navigator.pop(context);
  421. fun(file);
  422. },
  423. ),
  424. ),
  425. );
  426. }
  427. //粘贴要复制的文件
  428. //左右两边的加号
  429. void moreAction(int side) {
  430. String destinationDir = '';
  431. if (side == -1) {
  432. destinationDir = mode.parentDir.parent.path;
  433. } else {
  434. destinationDir = mode.parentDir.path;
  435. }
  436. showModalBottomSheet(
  437. backgroundColor: Color(0x00000000),
  438. context: context,
  439. builder: (BuildContext context) {
  440. return BackdropFilter(
  441. filter: ImageFilter.blur(
  442. sigmaX: 5.0,
  443. sigmaY: 5.0,
  444. ),
  445. child: Container(
  446. child: Column(
  447. mainAxisSize: MainAxisSize.min,
  448. crossAxisAlignment: side == -1
  449. ? CrossAxisAlignment.start
  450. : CrossAxisAlignment.end,
  451. children: <Widget>[
  452. mode.copyMode | mode.cutMode
  453. ? Column(
  454. children: <Widget>[
  455. Container(
  456. width: 170,
  457. child: Card(
  458. margin: EdgeInsets.only(
  459. left: 5, right: 5, bottom: 20),
  460. child: InkWell(
  461. child: Center(
  462. child: Padding(
  463. padding: EdgeInsets.all(10),
  464. child: Text('粘贴',
  465. style: TextStyle(color: Colors.blue)),
  466. ),
  467. ),
  468. onTap: () {
  469. print('copyTempFile-->file_manager:' +
  470. mode.copyTempFile.path.toString());
  471. if (mode.copyTempFile.statSync().type ==
  472. FileSystemEntityType.file) {
  473. String path = mode.copyTempFile.path;
  474. File temp = mode.copyTempFile;
  475. print('剪切的文件 ' + temp.path);
  476. temp.copy(destinationDir +
  477. '/' +
  478. temp.path.substring(
  479. temp.parent.path.length + 1));
  480. mode.copyTempFile = null; //复制之后取消复制模式
  481. if (mode.cutMode) {
  482. temp = File(path);
  483. temp.delete();
  484. print('删除' + temp.path);
  485. }
  486. if (side == -1) {
  487. new Operation(
  488. leftFiles, rightFiles, context,
  489. uiShouldChange: uiShouldChange,
  490. mode: mode)
  491. .initPathFiles(
  492. mode.parentDir.path, -3);
  493. } else {
  494. new Operation(
  495. leftFiles, rightFiles, context,
  496. uiShouldChange: uiShouldChange,
  497. mode: mode)
  498. .initPathFiles(
  499. mode.parentDir.path, -3);
  500. }
  501. mode.copyMode = false;
  502. mode.cutMode = false;
  503. Navigator.pop(context);
  504. }
  505. },
  506. ),
  507. ),
  508. ),
  509. Container(
  510. width: 170,
  511. child: Card(
  512. margin: EdgeInsets.only(
  513. left: 5, right: 5, bottom: 20),
  514. child: InkWell(
  515. child: Center(
  516. child: Padding(
  517. padding: EdgeInsets.all(10),
  518. child: Text('取消',
  519. style: TextStyle(
  520. color: Colors.cyanAccent)),
  521. ),
  522. ),
  523. onTap: () {
  524. mode.copyMode = false;
  525. mode.copyTempFile = null; //取消复制模式
  526. Navigator.pop(context);
  527. },
  528. ),
  529. ),
  530. )
  531. ],
  532. )
  533. : Container(
  534. width: 0,
  535. ),
  536. WidgetItem().returnFileOperateButton(context, cardColor,
  537. (file, type) => null, '新建文件', null, null)
  538. ],
  539. ),
  540. ),
  541. );
  542. });
  543. }
  544. //切换favorite
  545. void listFavorite() {
  546. setState(() {
  547. leftFiles.clear();
  548. rightFiles.clear();
  549. if (common.favoriteFileList != null) {
  550. for (var fileItem in common.favoriteFileList) {
  551. print(fileItem);
  552. leftFiles.add(new File(fileItem));
  553. }
  554. }
  555. });
  556. }
  557. }