file_info_dialog.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. import 'package:exif/exif.dart';
  2. import 'package:flutter/cupertino.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:photos/core/configuration.dart';
  5. import 'package:photos/models/file.dart';
  6. import 'package:photos/models/file_type.dart';
  7. import 'package:photos/services/collections_service.dart';
  8. import 'package:photos/ui/exif_info_dialog.dart';
  9. import 'package:photos/utils/date_time_util.dart';
  10. import 'package:photos/utils/exif_util.dart';
  11. import 'package:photos/utils/file_util.dart';
  12. import 'package:photos/utils/magic_util.dart';
  13. import 'package:photos/utils/toast_util.dart';
  14. class FileInfoWidget extends StatefulWidget {
  15. final File file;
  16. const FileInfoWidget(
  17. this.file, {
  18. Key key,
  19. }) : super(key: key);
  20. @override
  21. _FileInfoWidgetState createState() => _FileInfoWidgetState();
  22. }
  23. class _FileInfoWidgetState extends State<FileInfoWidget> {
  24. Map<String, IfdTag> _exif;
  25. bool _isImage = false;
  26. @override
  27. void initState() {
  28. _isImage = widget.file.fileType == FileType.image ||
  29. widget.file.fileType == FileType.livePhoto;
  30. if (_isImage) {
  31. getExif(widget.file).then((exif) {
  32. setState(() {
  33. _exif = exif;
  34. });
  35. });
  36. }
  37. super.initState();
  38. }
  39. @override
  40. Widget build(BuildContext context) {
  41. final file = widget.file;
  42. var items = <Widget>[
  43. Row(
  44. children: [
  45. Icon(
  46. Icons.calendar_today_outlined,
  47. color: Colors.white.withOpacity(0.85),
  48. ),
  49. Padding(padding: EdgeInsets.all(4)),
  50. Text(
  51. getFormattedTime(
  52. DateTime.fromMicrosecondsSinceEpoch(file.creationTime),
  53. ),
  54. style: TextStyle(
  55. color: Colors.white.withOpacity(0.85),
  56. ),
  57. ),
  58. ],
  59. ),
  60. Padding(padding: EdgeInsets.all(6)),
  61. Row(
  62. children: [
  63. Icon(
  64. Icons.folder_outlined,
  65. color: Colors.white.withOpacity(0.85),
  66. ),
  67. Padding(padding: EdgeInsets.all(4)),
  68. Text(
  69. file.deviceFolder ??
  70. CollectionsService.instance
  71. .getCollectionByID(file.collectionID)
  72. .name,
  73. style: TextStyle(
  74. color: Colors.white.withOpacity(0.85),
  75. ),
  76. ),
  77. ],
  78. ),
  79. Padding(padding: EdgeInsets.all(6)),
  80. ];
  81. items.addAll(
  82. [
  83. Row(
  84. children: [
  85. Icon(
  86. Icons.sd_storage_outlined,
  87. color: Colors.white.withOpacity(0.85),
  88. ),
  89. Padding(padding: EdgeInsets.all(4)),
  90. _getFileSize(),
  91. ],
  92. ),
  93. Padding(padding: EdgeInsets.all(6)),
  94. ],
  95. );
  96. if (file.localID != null && !_isImage) {
  97. items.addAll(
  98. [
  99. Row(
  100. children: [
  101. Icon(
  102. Icons.timer_outlined,
  103. color: Colors.white.withOpacity(0.85),
  104. ),
  105. Padding(padding: EdgeInsets.all(4)),
  106. FutureBuilder(
  107. future: file.getAsset(),
  108. builder: (context, snapshot) {
  109. if (snapshot.hasData) {
  110. return Text(
  111. snapshot.data.videoDuration.toString().split(".")[0],
  112. style: TextStyle(
  113. color: Colors.white.withOpacity(0.85),
  114. ),
  115. );
  116. } else {
  117. return Center(
  118. child: SizedBox.fromSize(
  119. size: Size.square(24),
  120. child: CupertinoActivityIndicator(
  121. radius: 8,
  122. ),
  123. ),
  124. );
  125. }
  126. },
  127. ),
  128. ],
  129. ),
  130. Padding(padding: EdgeInsets.all(6)),
  131. ],
  132. );
  133. }
  134. if (_isImage && _exif != null) {
  135. items.add(_getExifWidgets(_exif));
  136. }
  137. if (file.uploadedFileID != null && file.updationTime != null) {
  138. items.addAll(
  139. [
  140. Row(
  141. children: [
  142. Icon(
  143. Icons.cloud_upload_outlined,
  144. color: Colors.white.withOpacity(0.85),
  145. ),
  146. Padding(padding: EdgeInsets.all(4)),
  147. Text(
  148. getFormattedTime(
  149. DateTime.fromMicrosecondsSinceEpoch(file.updationTime)),
  150. style: TextStyle(
  151. color: Colors.white.withOpacity(0.85),
  152. ),
  153. ),
  154. ],
  155. ),
  156. ],
  157. );
  158. }
  159. items.add(
  160. Padding(padding: EdgeInsets.all(12)),
  161. );
  162. items.add(
  163. Row(
  164. mainAxisAlignment:
  165. _isImage ? MainAxisAlignment.spaceBetween : MainAxisAlignment.end,
  166. children: _getActions(),
  167. ),
  168. );
  169. Widget titleContent;
  170. if (file.uploadedFileID == null ||
  171. file.ownerID != Configuration.instance.getUserID()) {
  172. titleContent = Text(file.getDisplayName());
  173. } else {
  174. titleContent = InkWell(
  175. child: Row(
  176. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  177. children: [
  178. Flexible(
  179. child: Text(
  180. file.getDisplayName(),
  181. ),
  182. ),
  183. Padding(padding: EdgeInsets.all(8)),
  184. Icon(
  185. Icons.edit,
  186. color: Colors.white.withOpacity(0.85),
  187. ),
  188. ],
  189. ),
  190. onTap: () async {
  191. await editFilename(context, file);
  192. setState(() {});
  193. },
  194. );
  195. }
  196. return AlertDialog(
  197. title: titleContent,
  198. content: SingleChildScrollView(
  199. child: ListBody(
  200. children: items,
  201. ),
  202. ),
  203. );
  204. }
  205. List<Widget> _getActions() {
  206. final List<Widget> actions = [];
  207. if (_isImage) {
  208. if (_exif == null) {
  209. actions.add(
  210. TextButton(
  211. child: Row(
  212. mainAxisAlignment: MainAxisAlignment.spaceAround,
  213. children: [
  214. Center(
  215. child: SizedBox.fromSize(
  216. size: Size.square(24),
  217. child: CupertinoActivityIndicator(
  218. radius: 8,
  219. ),
  220. ),
  221. ),
  222. Padding(padding: EdgeInsets.all(4)),
  223. Text(
  224. "exif",
  225. style: TextStyle(
  226. color: Colors.white.withOpacity(0.85),
  227. ),
  228. ),
  229. ],
  230. ),
  231. onPressed: () {
  232. showDialog(
  233. context: context,
  234. builder: (BuildContext context) {
  235. return ExifInfoDialog(widget.file);
  236. },
  237. barrierColor: Colors.black87,
  238. );
  239. },
  240. ),
  241. );
  242. } else if (_exif.isNotEmpty) {
  243. actions.add(
  244. TextButton(
  245. child: Row(
  246. mainAxisAlignment: MainAxisAlignment.spaceAround,
  247. children: [
  248. Icon(
  249. Icons.feed_outlined,
  250. color: Colors.white.withOpacity(0.85),
  251. ),
  252. Padding(padding: EdgeInsets.all(4)),
  253. Text(
  254. "view exif",
  255. style: TextStyle(
  256. color: Colors.white.withOpacity(0.85),
  257. ),
  258. ),
  259. ],
  260. ),
  261. onPressed: () {
  262. showDialog(
  263. context: context,
  264. builder: (BuildContext context) {
  265. return ExifInfoDialog(widget.file);
  266. },
  267. barrierColor: Colors.black87,
  268. );
  269. },
  270. ),
  271. );
  272. } else {
  273. actions.add(
  274. TextButton(
  275. child: Row(
  276. mainAxisAlignment: MainAxisAlignment.spaceAround,
  277. children: [
  278. Icon(
  279. Icons.feed_outlined,
  280. color: Colors.white.withOpacity(0.5),
  281. ),
  282. Padding(padding: EdgeInsets.all(4)),
  283. Text(
  284. "no exif",
  285. style: TextStyle(
  286. color: Colors.white.withOpacity(0.5),
  287. ),
  288. ),
  289. ],
  290. ),
  291. onPressed: () {
  292. showToast("this image has no exif data");
  293. },
  294. ),
  295. );
  296. }
  297. }
  298. actions.add(
  299. TextButton(
  300. child: Text(
  301. "close",
  302. style: TextStyle(
  303. color: Colors.white.withOpacity(0.8),
  304. ),
  305. ),
  306. onPressed: () {
  307. Navigator.of(context, rootNavigator: true).pop('dialog');
  308. },
  309. ),
  310. );
  311. return actions;
  312. }
  313. Widget _getExifWidgets(Map<String, IfdTag> exif) {
  314. final focalLength = exif["EXIF FocalLength"] != null
  315. ? (exif["EXIF FocalLength"].values.toList()[0] as Ratio).numerator /
  316. (exif["EXIF FocalLength"].values.toList()[0] as Ratio).denominator
  317. : null;
  318. final fNumber = exif["EXIF FNumber"] != null
  319. ? (exif["EXIF FNumber"].values.toList()[0] as Ratio).numerator /
  320. (exif["EXIF FNumber"].values.toList()[0] as Ratio).denominator
  321. : null;
  322. final List<Widget> children = [];
  323. if (exif["EXIF ExifImageWidth"] != null &&
  324. exif["EXIF ExifImageLength"] != null) {
  325. children.addAll([
  326. Row(
  327. children: [
  328. Icon(
  329. Icons.photo_size_select_actual_outlined,
  330. color: Colors.white.withOpacity(0.85),
  331. ),
  332. Padding(padding: EdgeInsets.all(4)),
  333. Text(
  334. exif["EXIF ExifImageWidth"].toString() +
  335. " x " +
  336. exif["EXIF ExifImageLength"].toString(),
  337. style: TextStyle(
  338. color: Colors.white.withOpacity(0.85),
  339. ),
  340. ),
  341. ],
  342. ),
  343. Padding(padding: EdgeInsets.all(6)),
  344. ]);
  345. } else if (exif["Image ImageWidth"] != null &&
  346. exif["Image ImageLength"] != null) {
  347. children.addAll([
  348. Row(
  349. children: [
  350. Icon(
  351. Icons.photo_size_select_actual_outlined,
  352. color: Colors.white.withOpacity(0.85),
  353. ),
  354. Padding(padding: EdgeInsets.all(4)),
  355. Text(
  356. exif["Image ImageWidth"].toString() +
  357. " x " +
  358. exif["Image ImageLength"].toString(),
  359. style: TextStyle(
  360. color: Colors.white.withOpacity(0.85),
  361. ),
  362. ),
  363. ],
  364. ),
  365. Padding(padding: EdgeInsets.all(6)),
  366. ]);
  367. }
  368. if (exif["Image Make"] != null && exif["Image Model"] != null) {
  369. children.addAll(
  370. [
  371. Row(
  372. children: [
  373. Icon(
  374. Icons.camera_outlined,
  375. color: Colors.white.withOpacity(0.85),
  376. ),
  377. Padding(padding: EdgeInsets.all(4)),
  378. Flexible(
  379. child: Text(
  380. exif["Image Make"].toString() +
  381. " " +
  382. exif["Image Model"].toString(),
  383. style: TextStyle(
  384. color: Colors.white.withOpacity(0.85),
  385. ),
  386. overflow: TextOverflow.clip,
  387. ),
  388. ),
  389. ],
  390. ),
  391. Padding(padding: EdgeInsets.all(6)),
  392. ],
  393. );
  394. }
  395. if (fNumber != null) {
  396. children.addAll([
  397. Row(
  398. children: [
  399. Icon(
  400. CupertinoIcons.f_cursive,
  401. color: Colors.white.withOpacity(0.85),
  402. ),
  403. Padding(padding: EdgeInsets.all(4)),
  404. Text(
  405. fNumber.toString(),
  406. style: TextStyle(
  407. color: Colors.white.withOpacity(0.85),
  408. ),
  409. ),
  410. ],
  411. ),
  412. Padding(padding: EdgeInsets.all(6)),
  413. ]);
  414. }
  415. if (focalLength != null) {
  416. children.addAll([
  417. Row(
  418. children: [
  419. Icon(
  420. Icons.center_focus_strong_outlined,
  421. color: Colors.white.withOpacity(0.85),
  422. ),
  423. Padding(padding: EdgeInsets.all(4)),
  424. Text(focalLength.toString() + " mm",
  425. style: TextStyle(
  426. color: Colors.white.withOpacity(0.85),
  427. )),
  428. ],
  429. ),
  430. Padding(padding: EdgeInsets.all(6)),
  431. ]);
  432. }
  433. if (exif["EXIF ExposureTime"] != null) {
  434. children.addAll([
  435. Row(
  436. children: [
  437. Icon(
  438. Icons.shutter_speed,
  439. color: Colors.white.withOpacity(0.85),
  440. ),
  441. Padding(padding: EdgeInsets.all(4)),
  442. Text(
  443. exif["EXIF ExposureTime"].toString(),
  444. style: TextStyle(
  445. color: Colors.white.withOpacity(0.85),
  446. ),
  447. ),
  448. ],
  449. ),
  450. Padding(padding: EdgeInsets.all(6)),
  451. ]);
  452. }
  453. return Column(
  454. children: children,
  455. );
  456. }
  457. Widget _getFileSize() {
  458. return FutureBuilder(
  459. future: getFile(widget.file).then((f) => f.length()),
  460. builder: (context, snapshot) {
  461. if (snapshot.hasData) {
  462. return Text(
  463. (snapshot.data / (1024 * 1024)).toStringAsFixed(2) + " MB",
  464. style: TextStyle(
  465. color: Colors.white.withOpacity(0.85),
  466. ),
  467. );
  468. } else {
  469. return Center(
  470. child: SizedBox.fromSize(
  471. size: Size.square(24),
  472. child: CupertinoActivityIndicator(
  473. radius: 8,
  474. ),
  475. ),
  476. );
  477. }
  478. },
  479. );
  480. }
  481. }