Skip to content

Commit 1af0456

Browse files
committed
feat: Buat fitur download photo di halaman photo_view_page.dart
Fitur download ini dibuat dalam 2 pengkondisian yaitu, jika file photo-nya ada di server dan ada di lokal. Untuk file photo yang ada di server ini untuk fitur download photo yang sudah naik fotonya ke server. Sementara, untuk file photo yang di lokal ini berarti, file photo-nya masih di lokal.
1 parent c1acfed commit 1af0456

File tree

1 file changed

+260
-77
lines changed

1 file changed

+260
-77
lines changed

lib/feature/presentation/page/photo_view/photo_view_page.dart

Lines changed: 260 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
11
import 'dart:io';
22

3+
import 'package:dio/dio.dart';
34
import 'package:dipantau_desktop_client/core/util/enum/global_variable.dart';
45
import 'package:dipantau_desktop_client/core/util/enum/user_role.dart';
56
import 'package:dipantau_desktop_client/core/util/images.dart';
67
import 'package:dipantau_desktop_client/core/util/shared_preferences_manager.dart';
8+
import 'package:dipantau_desktop_client/core/util/widget_helper.dart';
79
import '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';
811
import 'package:easy_localization/easy_localization.dart';
912
import 'package:flutter/material.dart';
13+
import 'package:path_provider/path_provider.dart';
1014
import 'package:photo_view/photo_view.dart';
1115
import 'package:photo_view/photo_view_gallery.dart';
1216

1317
class 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 {
2936
class _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

Comments
 (0)