Skip to content

Commit 4f2baf0

Browse files
committed
feat: save image
Closes: #178
1 parent 13beb0b commit 4f2baf0

File tree

4 files changed

+74
-3
lines changed

4 files changed

+74
-3
lines changed

lib/i18n/en.i18n.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,10 @@
897897
"checkDetail": "Check details",
898898
"copyImageUrl": "Copy image url",
899899
"reloadImage": "ReloadImage",
900-
"openLink": "Open link"
900+
"openLink": "Open link",
901+
"saveImage": "Save image",
902+
"imageSaved": "Image saved at ${filePath: String}",
903+
"failedToSaveImage": "Failed to save image"
901904
},
902905
"init": {
903906
"v1DeleteLegacyData": {

lib/i18n/zh-CN.i18n.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,10 @@
897897
"checkDetail": "查看详情",
898898
"copyImageUrl": "复制图片链接",
899899
"reloadImage": "重新加载图片",
900-
"openLink": "打开链接"
900+
"openLink": "打开链接",
901+
"saveImage": "保存图片",
902+
"imageSaved": "图片已保存至 ${filePath: String}",
903+
"failedToSaveImage": "无法保存图片"
901904
},
902905
"init": {
903906
"v1DeleteLegacyData": {

lib/i18n/zh-TW.i18n.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,10 @@
897897
"checkDetail": "看詳情",
898898
"copyImageUrl": "複製圖片連結",
899899
"reloadImage": "重新載入圖片",
900-
"openLink": "開啟連結"
900+
"openLink": "開啟連結",
901+
"saveImage": "儲存圖片",
902+
"imageSaved": "圖片已儲存至 ${filePath: String}",
903+
"failedToSaveImage": "無法儲存圖片"
901904
},
902905
"init": {
903906
"v1DeleteLegacyData": {

lib/utils/show_bottom_sheet.dart

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1+
import 'dart:io';
2+
import 'dart:typed_data';
3+
4+
import 'package:file_picker/file_picker.dart';
15
import 'package:flutter/material.dart';
26
import 'package:flutter_bloc/flutter_bloc.dart';
7+
import 'package:fpdart/fpdart.dart' show None, Some;
38
import 'package:go_router/go_router.dart';
49
import 'package:smooth_sheets/smooth_sheets.dart';
510
import 'package:tsdm_client/constants/layout.dart';
611
import 'package:tsdm_client/extensions/build_context.dart';
12+
import 'package:tsdm_client/extensions/string.dart';
713
import 'package:tsdm_client/features/cache/bloc/image_cache_trigger_cubit.dart';
814
import 'package:tsdm_client/i18n/strings.g.dart';
15+
import 'package:tsdm_client/instance.dart';
916
import 'package:tsdm_client/routes/screen_paths.dart';
17+
import 'package:tsdm_client/shared/providers/image_cache_provider/image_cache_provider.dart';
18+
import 'package:tsdm_client/shared/providers/image_cache_provider/models/models.dart';
1019
import 'package:tsdm_client/utils/clipboard.dart';
20+
import 'package:tsdm_client/utils/platform.dart';
21+
import 'package:tsdm_client/utils/show_toast.dart';
1122
import 'package:tsdm_client/widgets/network_indicator_image.dart';
1223

1324
/// Show a bottom sheet with given [title] and build children
@@ -150,6 +161,57 @@ Future<void> showImageActionBottomSheet({
150161
}
151162
},
152163
),
164+
ListTile(
165+
leading: const Icon(Icons.save_outlined),
166+
title: Text(tr.saveImage),
167+
onTap: () async {
168+
final imageProvider = getIt.get<ImageCacheProvider>();
169+
switch (await imageProvider.getOrMakeCache(ImageCacheGeneralRequest(imageUrl))) {
170+
case None():
171+
{
172+
if (context.mounted) {
173+
showSnackBar(context: context, message: tr.failedToSaveImage);
174+
}
175+
}
176+
case Some<Uint8List>(value: final data):
177+
{
178+
final fileName =
179+
imageUrl.tryParseAsUri()?.pathSegments.lastOrNull ??
180+
'tsdm_client_image_${DateTime.now().microsecondsSinceEpoch}.jpg';
181+
if (isDesktop) {
182+
// On desktop platforms, `saveFiles` only return the selected path.
183+
final filePath = await FilePicker.platform.saveFile(dialogTitle: tr.saveImage, fileName: fileName);
184+
if (filePath == null) {
185+
return;
186+
}
187+
188+
await File(filePath).writeAsBytes(data, flush: true);
189+
if (context.mounted) {
190+
context.pop();
191+
showSnackBar(
192+
context: context,
193+
message: tr.imageSaved(filePath: filePath),
194+
);
195+
}
196+
} else {
197+
// Mobile in one step.
198+
final filePath = await FilePicker.platform.saveFile(
199+
dialogTitle: tr.saveImage,
200+
fileName: fileName,
201+
bytes: data,
202+
);
203+
if (filePath != null && context.mounted) {
204+
context.pop();
205+
showSnackBar(
206+
context: context,
207+
message: tr.imageSaved(filePath: filePath),
208+
);
209+
}
210+
}
211+
}
212+
}
213+
},
214+
),
153215
],
154216
);
155217
}

0 commit comments

Comments
 (0)