11import 'dart:io' ;
22
3+ import 'package:dio/dio.dart' ;
34import 'package:dipantau_desktop_client/core/util/enum/global_variable.dart' ;
45import 'package:dipantau_desktop_client/core/util/enum/user_role.dart' ;
56import 'package:dipantau_desktop_client/core/util/images.dart' ;
67import 'package:dipantau_desktop_client/core/util/shared_preferences_manager.dart' ;
8+ import 'package:dipantau_desktop_client/core/util/widget_helper.dart' ;
79import 'package:dipantau_desktop_client/feature/data/model/track_user/track_user_response.dart' ;
10+ import 'package:dipantau_desktop_client/feature/presentation/widget/widget_loading_center_full_screen.dart' ;
811import 'package:easy_localization/easy_localization.dart' ;
912import 'package:flutter/material.dart' ;
13+ import 'package:path_provider/path_provider.dart' ;
1014import 'package:photo_view/photo_view.dart' ;
1115import 'package:photo_view/photo_view_gallery.dart' ;
1216
1317class PhotoViewPage extends StatefulWidget {
1418 static const routePath = '/photo-view' ;
1519 static const routeName = 'photo-view' ;
1620 static const parameterListPhotos = 'list_photos' ;
21+ static const parameterIsShowIconDownload = 'is_show_icon_download' ;
1722
1823 final List <ItemFileTrackUserResponse >? listPhotos;
24+ final bool ? isShowIconDownload;
1925
2026 PhotoViewPage ({
2127 Key ? key,
2228 required this .listPhotos,
29+ required this .isShowIconDownload,
2330 }) : super (key: key);
2431
2532 @override
@@ -29,11 +36,14 @@ class PhotoViewPage extends StatefulWidget {
2936class _PhotoViewPageState extends State <PhotoViewPage > {
3037 final pageController = PageController ();
3138 final listPhotos = < ItemFileTrackUserResponse > [];
39+ final valueNotifierLoadingDownload = ValueNotifier (false );
40+ final widgetHelper = WidgetHelper ();
3241
3342 var indexSelectedPhoto = 0 ;
3443 UserRole ? userRole;
3544 var isBlurSettingEnabled = false ;
3645 var isBlurPreviewEnabled = false ;
46+ var isShowIconDownload = false ;
3747
3848 @override
3949 void initState () {
@@ -44,97 +54,270 @@ class _PhotoViewPageState extends State<PhotoViewPage> {
4454 }
4555 isBlurSettingEnabled = listPhotos.where ((element) => (element.urlBlur ?? '' ).isNotEmpty).isNotEmpty;
4656 isBlurPreviewEnabled = isBlurSettingEnabled;
57+ isShowIconDownload = widget.isShowIconDownload ?? false ;
4758 super .initState ();
4859 }
4960
5061 @override
5162 Widget build (BuildContext context) {
52- return listPhotos.isEmpty
53- ? Center (
54- child: Text ('no_data_to_display' .tr ()),
55- )
56- : Stack (
57- children: [
58- PhotoViewGallery .builder (
59- pageController: pageController,
60- scrollPhysics: const BouncingScrollPhysics (),
61- builder: (BuildContext context, int index) {
62- var photo = '' ;
63- if (isBlurPreviewEnabled) {
64- photo = listPhotos[index].urlBlur ?? '' ;
65- } else {
66- photo = listPhotos[index].url ?? '' ;
67- }
68- return photo.startsWith ('http' )
69- ? PhotoViewGalleryPageOptions (
70- imageProvider: NetworkImage (photo),
71- initialScale: PhotoViewComputedScale .contained,
72- heroAttributes: PhotoViewHeroAttributes (
73- tag: photo,
74- ),
75- )
76- : PhotoViewGalleryPageOptions (
77- imageProvider: FileImage (File (photo)),
78- initialScale: PhotoViewComputedScale .contained,
79- heroAttributes: PhotoViewHeroAttributes (
80- tag: photo,
81- ),
82- );
83- },
84- loadingBuilder: (context, loadingProgress) {
85- final cumulativeBytesLoaded = loadingProgress? .cumulativeBytesLoaded ?? 0 ;
86- return Center (
87- child: CircularProgressIndicator (
88- strokeWidth: 1 ,
89- value: loadingProgress? .expectedTotalBytes != null
90- ? cumulativeBytesLoaded / loadingProgress! .expectedTotalBytes!
91- : null ,
92- ),
93- );
94- },
95- itemCount: listPhotos.length,
96- onPageChanged: (index) {
97- setState (() => indexSelectedPhoto = index);
98- },
99- ),
100- buildWidgetIconClose (),
101- buildWidgetIconPreviewSetting (),
102- Align (
103- alignment: Alignment .bottomCenter,
104- child: buildWidgetSliderPreviewPhoto (),
63+ return Scaffold (
64+ body: listPhotos.isEmpty
65+ ? Center (
66+ child: Text ('no_data_to_display' .tr ()),
67+ )
68+ : Stack (
69+ children: [
70+ PhotoViewGallery .builder (
71+ pageController: pageController,
72+ scrollPhysics: const BouncingScrollPhysics (),
73+ builder: (BuildContext context, int index) {
74+ var photo = '' ;
75+ if (isBlurPreviewEnabled) {
76+ photo = listPhotos[index].urlBlur ?? '' ;
77+ } else {
78+ photo = listPhotos[index].url ?? '' ;
79+ }
80+ return photo.startsWith ('http' )
81+ ? PhotoViewGalleryPageOptions (
82+ imageProvider: NetworkImage (photo),
83+ initialScale: PhotoViewComputedScale .contained,
84+ heroAttributes: PhotoViewHeroAttributes (
85+ tag: photo,
86+ ),
87+ )
88+ : PhotoViewGalleryPageOptions (
89+ imageProvider: FileImage (File (photo)),
90+ initialScale: PhotoViewComputedScale .contained,
91+ heroAttributes: PhotoViewHeroAttributes (
92+ tag: photo,
93+ ),
94+ );
95+ },
96+ loadingBuilder: (context, loadingProgress) {
97+ final cumulativeBytesLoaded = loadingProgress? .cumulativeBytesLoaded ?? 0 ;
98+ return Center (
99+ child: CircularProgressIndicator (
100+ strokeWidth: 1 ,
101+ value: loadingProgress? .expectedTotalBytes != null
102+ ? cumulativeBytesLoaded / loadingProgress! .expectedTotalBytes!
103+ : null ,
104+ ),
105+ );
106+ },
107+ itemCount: listPhotos.length,
108+ onPageChanged: (index) {
109+ setState (() => indexSelectedPhoto = index);
110+ },
111+ ),
112+ buildWidgetIconClose (),
113+ buildWidgetActionTopEnd (),
114+ Align (
115+ alignment: Alignment .bottomCenter,
116+ child: buildWidgetSliderPreviewPhoto (),
117+ ),
118+ buildWidgetLoadingFullScreen (),
119+ ],
120+ ),
121+ );
122+ }
123+
124+ Widget buildWidgetLoadingFullScreen () {
125+ return ValueListenableBuilder (
126+ valueListenable: valueNotifierLoadingDownload,
127+ builder: (BuildContext context, bool isShowLoading, _) {
128+ if (isShowLoading) {
129+ return const WidgetLoadingCenterFullScreen ();
130+ }
131+ return Container ();
132+ },
133+ );
134+ }
135+
136+ Widget buildWidgetActionTopEnd () {
137+ return Align (
138+ alignment: Alignment .topRight,
139+ child: Row (
140+ mainAxisSize: MainAxisSize .min,
141+ crossAxisAlignment: CrossAxisAlignment .start,
142+ children: [
143+ buildWidgetIconPreviewSetting (),
144+ buildWidgetIconDownload (),
145+ ],
146+ ),
147+ );
148+ }
149+
150+ Widget buildWidgetIconDownload () {
151+ if (! isShowIconDownload) {
152+ return Container ();
153+ }
154+
155+ return Container (
156+ decoration: BoxDecoration (
157+ shape: BoxShape .circle,
158+ color: Colors .black.withOpacity (.5 ),
159+ ),
160+ margin: const EdgeInsets .only (
161+ right: 8 ,
162+ top: 8 ,
163+ ),
164+ child: IconButton (
165+ onPressed: () async {
166+ final selectedPhoto = listPhotos[indexSelectedPhoto];
167+ final url = selectedPhoto.url ?? '' ;
168+
169+ final downloadDirectory = await getDownloadsDirectory ();
170+ final pathDownloadDirectory = downloadDirectory? .path;
171+ if ((pathDownloadDirectory == null || pathDownloadDirectory.isEmpty) && mounted) {
172+ widgetHelper.showDialogMessage (
173+ context,
174+ 'info' .tr (),
175+ 'download_directory_invalid' .tr (),
176+ );
177+ return ;
178+ }
179+
180+ if (url.startsWith ('http' )) {
181+ // download file dari url dan simpan ke directory download
182+ final splitUrl = url.split ('/' );
183+ if (splitUrl.isEmpty && mounted) {
184+ widgetHelper.showDialogMessage (
185+ context,
186+ 'info' .tr (),
187+ 'url_screenshot_invalid' .tr (),
188+ );
189+ return ;
190+ }
191+
192+ final itemUrl = splitUrl.last;
193+ final splitItemUrl = itemUrl.split ('?' );
194+ if (splitItemUrl.isEmpty && mounted) {
195+ widgetHelper.showDialogMessage (
196+ context,
197+ 'info' .tr (),
198+ 'screenshot_name_invalid' .tr (),
199+ );
200+ return ;
201+ }
202+
203+ valueNotifierLoadingDownload.value = true ;
204+ final filename = splitItemUrl.first;
205+ final response = await Dio ().get (
206+ url,
207+ options: Options (
208+ responseType: ResponseType .bytes,
105209 ),
106- ],
107- );
210+ );
211+ try {
212+ final fileNameAndPath = '$pathDownloadDirectory /$filename ' ;
213+ final file = File (fileNameAndPath);
214+ await file.writeAsBytes (response.data);
215+ if (mounted) {
216+ widgetHelper.showSnackBar (
217+ context,
218+ 'screenshot_downloaded_successfully' .tr (),
219+ );
220+ }
221+ } catch (error) {
222+ if (mounted) {
223+ widgetHelper.showDialogMessage (
224+ context,
225+ 'info' .tr (),
226+ 'something_went_wrong_with_message' .tr (
227+ args: [
228+ '$error ' ,
229+ ],
230+ ),
231+ );
232+ }
233+ } finally {
234+ valueNotifierLoadingDownload.value = false ;
235+ }
236+ } else {
237+ // copy file dari lokal ke download directory
238+ valueNotifierLoadingDownload.value = true ;
239+ final originalFile = File (url);
240+ try {
241+ if (! originalFile.existsSync () && mounted) {
242+ widgetHelper.showDialogMessage (
243+ context,
244+ 'info' .tr (),
245+ 'file_screenshot_doesnt_exists' .tr (),
246+ );
247+ return ;
248+ }
249+
250+ final splitPathOriginalFile = url.split ('/' );
251+ if (splitPathOriginalFile.isEmpty && mounted) {
252+ widgetHelper.showDialogMessage (
253+ context,
254+ 'info' .tr (),
255+ 'path_file_screenshot_invalid' .tr (),
256+ );
257+ return ;
258+ }
259+
260+ final filename = splitPathOriginalFile.last;
261+ final fileNameAndPath = '$pathDownloadDirectory /$filename ' ;
262+ final newFile = File (fileNameAndPath);
263+ await originalFile.copy (newFile.path);
264+ if (mounted) {
265+ widgetHelper.showSnackBar (
266+ context,
267+ 'screenshot_downloaded_successfully' .tr (),
268+ );
269+ }
270+ } catch (error) {
271+ if (mounted) {
272+ widgetHelper.showDialogMessage (
273+ context,
274+ 'info' .tr (),
275+ 'something_went_wrong_with_message' .tr (
276+ args: [
277+ '$error ' ,
278+ ],
279+ ),
280+ );
281+ }
282+ } finally {
283+ valueNotifierLoadingDownload.value = false ;
284+ }
285+ }
286+ },
287+ icon: const Icon (
288+ Icons .download,
289+ color: Colors .white,
290+ ),
291+ padding: const EdgeInsets .all (8 ),
292+ ),
293+ );
108294 }
109295
110296 Widget buildWidgetIconPreviewSetting () {
111297 if (! isBlurSettingEnabled || (userRole != UserRole .superAdmin)) {
112298 return Container ();
113299 }
114300
115- return Align (
116- alignment: Alignment .topRight,
117- child: Container (
118- decoration: BoxDecoration (
119- shape: BoxShape .circle,
120- color: Colors .black.withOpacity (.5 ),
121- ),
122- margin: const EdgeInsets .only (
123- right: 8 ,
124- top: 8 ,
125- ),
126- child: IconButton (
127- onPressed: () {
128- setState (() {
129- isBlurPreviewEnabled = ! isBlurPreviewEnabled;
130- });
131- },
132- icon: Icon (
133- isBlurPreviewEnabled ? Icons .visibility_off : Icons .visibility,
134- color: Colors .white,
135- ),
136- padding: const EdgeInsets .all (8 ),
301+ return Container (
302+ decoration: BoxDecoration (
303+ shape: BoxShape .circle,
304+ color: Colors .black.withOpacity (.5 ),
305+ ),
306+ margin: const EdgeInsets .only (
307+ right: 8 ,
308+ top: 8 ,
309+ ),
310+ child: IconButton (
311+ onPressed: () {
312+ setState (() {
313+ isBlurPreviewEnabled = ! isBlurPreviewEnabled;
314+ });
315+ },
316+ icon: Icon (
317+ isBlurPreviewEnabled ? Icons .visibility_off : Icons .visibility,
318+ color: Colors .white,
137319 ),
320+ padding: const EdgeInsets .all (8 ),
138321 ),
139322 );
140323 }
0 commit comments