file_info_dialog.dart 13 KB

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