mirror of
https://gitlab.com/comunic/comunicmobile
synced 2024-11-22 21:09:21 +00:00
Simplify image picking code
This commit is contained in:
parent
ea45bf828c
commit
e70aaabbc9
@ -60,17 +60,6 @@ class APIHelper {
|
|||||||
contentType: v.type,
|
contentType: v.type,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process picked files
|
|
||||||
for (final key in request.pickedFiles.keys) {
|
|
||||||
var v = request.pickedFiles[key];
|
|
||||||
data.files.add(MapEntry(
|
|
||||||
key,
|
|
||||||
MultipartFile.fromBytes(
|
|
||||||
await v.readAsBytes(),
|
|
||||||
filename: v.path.split("/").last,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the request
|
// Execute the request
|
||||||
|
@ -17,7 +17,7 @@ class CommentsHelper {
|
|||||||
"content": comment.hasContent ? comment.content : "",
|
"content": comment.hasContent ? comment.content : "",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (comment.hasImage) request.addPickedFile("image", comment.image);
|
if (comment.hasImage) request.addBytesFile("image", comment.image);
|
||||||
|
|
||||||
final response = await request.execWithFiles();
|
final response = await request.execWithFiles();
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ class PostsHelper {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case PostKind.IMAGE:
|
case PostKind.IMAGE:
|
||||||
request.addPickedFile("image", post.image);
|
request.addBytesFile("image", post.image);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PostKind.WEB_LINK:
|
case PostKind.WEB_LINK:
|
||||||
|
@ -5,7 +5,8 @@ import 'package:comunic/models/data_conservation_policy_settings.dart';
|
|||||||
import 'package:comunic/models/general_settings.dart';
|
import 'package:comunic/models/general_settings.dart';
|
||||||
import 'package:comunic/models/new_emoji.dart';
|
import 'package:comunic/models/new_emoji.dart';
|
||||||
import 'package:comunic/models/security_settings.dart';
|
import 'package:comunic/models/security_settings.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
|
import '../models/api_request.dart';
|
||||||
|
|
||||||
/// Settings helper
|
/// Settings helper
|
||||||
///
|
///
|
||||||
@ -92,11 +93,10 @@ class SettingsHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Upload a new account image
|
/// Upload a new account image
|
||||||
static Future<bool> uploadAccountImage(PickedFile newImage) async =>
|
static Future<void> uploadAccountImage(BytesFile newImage) async =>
|
||||||
(await APIRequest(uri: "settings/upload_account_image", needLogin: true)
|
await APIRequest(uri: "settings/upload_account_image", needLogin: true)
|
||||||
.addPickedFile("picture", newImage)
|
.addBytesFile("picture", newImage)
|
||||||
.execWithFiles())
|
.execWithFilesAndThrow();
|
||||||
.isOK;
|
|
||||||
|
|
||||||
/// Upload a new account image from memory
|
/// Upload a new account image from memory
|
||||||
static Future<bool> uploadAccountImageFromMemory(List<int> bytes) async =>
|
static Future<bool> uploadAccountImageFromMemory(List<int> bytes) async =>
|
||||||
@ -128,13 +128,12 @@ class SettingsHelper {
|
|||||||
|
|
||||||
/// Upload a new custom emoji
|
/// Upload a new custom emoji
|
||||||
static Future<void> uploadNewCustomEmoji(NewEmoji newEmoji) async =>
|
static Future<void> uploadNewCustomEmoji(NewEmoji newEmoji) async =>
|
||||||
(await APIRequest(
|
await APIRequest(
|
||||||
uri: "settings/upload_custom_emoji",
|
uri: "settings/upload_custom_emoji",
|
||||||
needLogin: true,
|
needLogin: true,
|
||||||
args: {"shortcut": newEmoji.shortcut})
|
args: {"shortcut": newEmoji.shortcut})
|
||||||
.addPickedFile("image", newEmoji.image)
|
.addBytesFile("image", newEmoji.image)
|
||||||
.execWithFiles())
|
.execWithFilesAndThrow();
|
||||||
.assertOk();
|
|
||||||
|
|
||||||
/// Delete a custom emoji
|
/// Delete a custom emoji
|
||||||
///
|
///
|
||||||
@ -220,7 +219,8 @@ class SettingsHelper {
|
|||||||
/// Throws in case of failure
|
/// Throws in case of failure
|
||||||
static Future<void> setDataConservationPolicy(
|
static Future<void> setDataConservationPolicy(
|
||||||
String password, DataConservationPolicySettings newSettings) async {
|
String password, DataConservationPolicySettings newSettings) async {
|
||||||
await APIRequest(uri: "settings/set_data_conservation_policy", needLogin: true)
|
await APIRequest(
|
||||||
|
uri: "settings/set_data_conservation_policy", needLogin: true)
|
||||||
.addString("password", password)
|
.addString("password", password)
|
||||||
.addInt("inactive_account_lifetime",
|
.addInt("inactive_account_lifetime",
|
||||||
newSettings.inactiveAccountLifeTime ?? 0)
|
newSettings.inactiveAccountLifeTime ?? 0)
|
||||||
|
@ -4,7 +4,6 @@ import 'package:comunic/helpers/api_helper.dart';
|
|||||||
import 'package:comunic/models/api_response.dart';
|
import 'package:comunic/models/api_response.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:http_parser/http_parser.dart';
|
import 'package:http_parser/http_parser.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
/// API Request model
|
/// API Request model
|
||||||
@ -32,7 +31,6 @@ class APIRequest {
|
|||||||
CancelToken cancelToken;
|
CancelToken cancelToken;
|
||||||
Map<String, String> args;
|
Map<String, String> args;
|
||||||
Map<String, File> files = Map();
|
Map<String, File> files = Map();
|
||||||
Map<String, PickedFile> pickedFiles = Map();
|
|
||||||
Map<String, BytesFile> bytesFiles = Map();
|
Map<String, BytesFile> bytesFiles = Map();
|
||||||
|
|
||||||
APIRequest({@required this.uri, this.needLogin = false, this.args})
|
APIRequest({@required this.uri, this.needLogin = false, this.args})
|
||||||
@ -73,11 +71,6 @@ class APIRequest {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
APIRequest addPickedFile(String name, PickedFile file) {
|
|
||||||
pickedFiles[name] = file;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
APIRequest addBytesFile(String name, BytesFile file) {
|
APIRequest addBytesFile(String name, BytesFile file) {
|
||||||
this.bytesFiles[name] = file;
|
this.bytesFiles[name] = file;
|
||||||
return this;
|
return this;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
import 'api_request.dart';
|
||||||
|
|
||||||
/// New comment information
|
/// New comment information
|
||||||
///
|
///
|
||||||
/// @author Pierre HUBERT
|
/// @author Pierre HUBERT
|
||||||
@ -8,7 +9,7 @@ import 'package:meta/meta.dart';
|
|||||||
class NewComment {
|
class NewComment {
|
||||||
final int postID;
|
final int postID;
|
||||||
final String content;
|
final String content;
|
||||||
final PickedFile image;
|
final BytesFile image;
|
||||||
|
|
||||||
const NewComment({
|
const NewComment({
|
||||||
@required this.postID,
|
@required this.postID,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
|
import 'api_request.dart';
|
||||||
|
|
||||||
/// New emoji information
|
/// New emoji information
|
||||||
///
|
///
|
||||||
@ -7,7 +8,7 @@ import 'package:image_picker/image_picker.dart';
|
|||||||
|
|
||||||
class NewEmoji {
|
class NewEmoji {
|
||||||
final String shortcut;
|
final String shortcut;
|
||||||
final PickedFile image;
|
final BytesFile image;
|
||||||
|
|
||||||
const NewEmoji({
|
const NewEmoji({
|
||||||
@required this.shortcut,
|
@required this.shortcut,
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import 'package:comunic/enums/post_kind.dart';
|
import 'package:comunic/enums/post_kind.dart';
|
||||||
import 'package:comunic/enums/post_target.dart';
|
import 'package:comunic/enums/post_target.dart';
|
||||||
import 'package:comunic/enums/post_visibility_level.dart';
|
import 'package:comunic/enums/post_visibility_level.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
import 'api_request.dart';
|
||||||
|
|
||||||
/// New post information
|
/// New post information
|
||||||
///
|
///
|
||||||
/// @author Pierre HUBERT
|
/// @author Pierre HUBERT
|
||||||
@ -27,7 +28,7 @@ class NewPost {
|
|||||||
final int targetID;
|
final int targetID;
|
||||||
final PostVisibilityLevel visibility;
|
final PostVisibilityLevel visibility;
|
||||||
final String content;
|
final String content;
|
||||||
final PickedFile image;
|
final BytesFile image;
|
||||||
final String url;
|
final String url;
|
||||||
final List<int> pdf;
|
final List<int> pdf;
|
||||||
final PostKind kind;
|
final PostKind kind;
|
||||||
|
@ -15,6 +15,9 @@ import 'package:identicon/identicon.dart';
|
|||||||
import 'package:random_string/random_string.dart';
|
import 'package:random_string/random_string.dart';
|
||||||
import 'package:settings_ui/settings_ui.dart';
|
import 'package:settings_ui/settings_ui.dart';
|
||||||
|
|
||||||
|
import '../../../utils/log_utils.dart';
|
||||||
|
import '../../../utils/ui_utils.dart';
|
||||||
|
|
||||||
/// Account image settings section
|
/// Account image settings section
|
||||||
///
|
///
|
||||||
/// @author Pierre Hubert
|
/// @author Pierre Hubert
|
||||||
@ -156,15 +159,16 @@ class _AccountImageSettingsScreenState
|
|||||||
|
|
||||||
/// Upload a new account image
|
/// Upload a new account image
|
||||||
void _uploadAccountImage() async {
|
void _uploadAccountImage() async {
|
||||||
final image = await pickImage(context);
|
try {
|
||||||
|
final image = await pickImage(context);
|
||||||
|
|
||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
|
|
||||||
if (!await SettingsHelper.uploadAccountImage(image)) {
|
await SettingsHelper.uploadAccountImage(image);
|
||||||
showSimpleSnack(context, tr("Could not upload your account image!"));
|
} catch (e, s) {
|
||||||
return;
|
logError(e, s);
|
||||||
|
snack(context, tr("Failed to upload new account image!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_key.currentState.refresh();
|
_key.currentState.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,9 @@ import 'package:comunic/utils/input_utils.dart';
|
|||||||
import 'package:comunic/utils/intl_utils.dart';
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:comunic/utils/ui_utils.dart';
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
|
import '../../../models/api_request.dart';
|
||||||
|
import '../../../utils/ui_utils.dart';
|
||||||
|
|
||||||
/// Emojies account settings
|
/// Emojies account settings
|
||||||
///
|
///
|
||||||
@ -136,7 +138,7 @@ class _NewCustomEmojiDialog extends StatefulWidget {
|
|||||||
|
|
||||||
class _NewCustomEmojiDialogState extends State<_NewCustomEmojiDialog> {
|
class _NewCustomEmojiDialogState extends State<_NewCustomEmojiDialog> {
|
||||||
final _controller = TextEditingController();
|
final _controller = TextEditingController();
|
||||||
PickedFile _file;
|
BytesFile _file;
|
||||||
|
|
||||||
bool get _hasImage => _file != null;
|
bool get _hasImage => _file != null;
|
||||||
|
|
||||||
@ -209,6 +211,7 @@ class _NewCustomEmojiDialogState extends State<_NewCustomEmojiDialog> {
|
|||||||
});
|
});
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
print("Could not pick an image! $e\n$stack");
|
print("Could not pick an image! $e\n$stack");
|
||||||
|
snack(context, tr("Failed to pick an image!"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,19 +259,19 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
|
|||||||
// Upload a new logo
|
// Upload a new logo
|
||||||
SettingsTile(
|
SettingsTile(
|
||||||
title: tr("Upload a new logo"),
|
title: tr("Upload a new logo"),
|
||||||
onPressed: (_) => _uploadNewLogo,
|
onPressed: (_) => _uploadNewLogo(),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Generate a new random logo
|
// Generate a new random logo
|
||||||
SettingsTile(
|
SettingsTile(
|
||||||
title: tr("Generate a new random logo"),
|
title: tr("Generate a new random logo"),
|
||||||
onPressed: (_) => _generateRandomLogo,
|
onPressed: (_) => _generateRandomLogo(),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Delete current logo
|
// Delete current logo
|
||||||
SettingsTile(
|
SettingsTile(
|
||||||
title: tr("Delete logo"),
|
title: tr("Delete logo"),
|
||||||
onPressed: (_) => _deleteLogo,
|
onPressed: (_) => _deleteLogo(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -281,8 +281,8 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
|
|||||||
void _uploadNewLogo() async {
|
void _uploadNewLogo() async {
|
||||||
try {
|
try {
|
||||||
final logo = await pickImage(context);
|
final logo = await pickImage(context);
|
||||||
final bytes = await logo.readAsBytes();
|
if (logo == null) return;
|
||||||
await _doUploadLogo(bytes);
|
await _doUploadLogo(logo.bytes);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
print("Could not upload new logo! $e\n$stack");
|
print("Could not upload new logo! $e\n$stack");
|
||||||
showSimpleSnack(context, tr("Could not upload new logo!"));
|
showSimpleSnack(context, tr("Could not upload new logo!"));
|
||||||
@ -328,7 +328,7 @@ class _GroupSettingsScreenState extends SafeState<GroupSettingsScreen> {
|
|||||||
tiles: [
|
tiles: [
|
||||||
SettingsTile(
|
SettingsTile(
|
||||||
title: tr("Delete group"),
|
title: tr("Delete group"),
|
||||||
onPressed: (_) => _deleteGroup,
|
onPressed: (_) => _deleteGroup(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -26,9 +26,11 @@ import 'package:comunic/utils/post_utils.dart';
|
|||||||
import 'package:comunic/utils/ui_utils.dart';
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
import '../../models/api_request.dart';
|
||||||
|
import '../../utils/log_utils.dart';
|
||||||
|
|
||||||
/// Single posts tile
|
/// Single posts tile
|
||||||
///
|
///
|
||||||
/// @author Pierre HUBERT
|
/// @author Pierre HUBERT
|
||||||
@ -77,7 +79,7 @@ class _PostTileState extends State<PostTile> {
|
|||||||
|
|
||||||
// Class members
|
// Class members
|
||||||
TextEditingController _commentController = TextEditingController();
|
TextEditingController _commentController = TextEditingController();
|
||||||
PickedFile _commentImage;
|
BytesFile _commentImage;
|
||||||
bool _submitting = false;
|
bool _submitting = false;
|
||||||
int _maxNumberOfCommentToShow = 10;
|
int _maxNumberOfCommentToShow = 10;
|
||||||
|
|
||||||
@ -493,11 +495,16 @@ class _PostTileState extends State<PostTile> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick a new image
|
try {
|
||||||
final newImage = await pickImage(context);
|
// Pick a new image
|
||||||
setState(() {
|
final newImage = await pickImage(context);
|
||||||
_commentImage = newImage;
|
setState(() {
|
||||||
});
|
_commentImage = newImage;
|
||||||
|
});
|
||||||
|
} catch (e, s) {
|
||||||
|
logError(e, s);
|
||||||
|
snack(context, tr("Failed to choose an image!"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Submit comment entered by the user
|
/// Submit comment entered by the user
|
||||||
|
@ -13,7 +13,10 @@ import 'package:comunic/utils/post_utils.dart';
|
|||||||
import 'package:comunic/utils/ui_utils.dart';
|
import 'package:comunic/utils/ui_utils.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
|
import '../../models/api_request.dart';
|
||||||
|
import '../../utils/log_utils.dart';
|
||||||
|
import '../../utils/ui_utils.dart';
|
||||||
|
|
||||||
/// Widget that allows to create posts
|
/// Widget that allows to create posts
|
||||||
///
|
///
|
||||||
@ -48,7 +51,7 @@ class _PostCreateFormWidgetState extends State<PostCreateFormWidget> {
|
|||||||
bool _isCreating = false;
|
bool _isCreating = false;
|
||||||
final TextEditingController _postTextController = TextEditingController();
|
final TextEditingController _postTextController = TextEditingController();
|
||||||
PostVisibilityLevel _postVisibilityLevel;
|
PostVisibilityLevel _postVisibilityLevel;
|
||||||
PickedFile _postImage;
|
BytesFile _postImage;
|
||||||
String _postURL;
|
String _postURL;
|
||||||
List<int> _postPDF;
|
List<int> _postPDF;
|
||||||
DateTime _timeEnd;
|
DateTime _timeEnd;
|
||||||
@ -239,15 +242,20 @@ class _PostCreateFormWidgetState extends State<PostCreateFormWidget> {
|
|||||||
|
|
||||||
/// Pick an image for the new post
|
/// Pick an image for the new post
|
||||||
Future<void> _pickImageForPost() async {
|
Future<void> _pickImageForPost() async {
|
||||||
final image = await pickImage(context);
|
try {
|
||||||
|
final image = await pickImage(context);
|
||||||
|
|
||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
|
|
||||||
_resetPostSelection();
|
_resetPostSelection();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
this._postImage = image;
|
this._postImage = image;
|
||||||
});
|
});
|
||||||
|
} catch (e, s) {
|
||||||
|
logError(e, s);
|
||||||
|
snack(context, tr("Failed to pick image for post!"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Choose a new URL for the post
|
/// Choose a new URL for the post
|
||||||
|
@ -1,54 +1,27 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:comunic/utils/intl_utils.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:random_string/random_string.dart';
|
import 'package:random_string/random_string.dart';
|
||||||
|
|
||||||
|
import '../models/api_request.dart';
|
||||||
|
import '../ui/dialogs/pick_file_dialog.dart';
|
||||||
|
|
||||||
/// Files utilities
|
/// Files utilities
|
||||||
///
|
///
|
||||||
/// @author Pierre HUBERT
|
/// @author Pierre HUBERT
|
||||||
|
|
||||||
enum _ChooseImageSource { GALLERY, CAMERA }
|
|
||||||
|
|
||||||
/// Ask the user to choose an image, either from the gallery or using the camera
|
/// Ask the user to choose an image, either from the gallery or using the camera
|
||||||
///
|
///
|
||||||
/// Returns null in case of failure
|
/// Throws an exception null in case of failure
|
||||||
Future<PickedFile> pickImage(BuildContext context) async {
|
Future<BytesFile> pickImage(BuildContext context) async {
|
||||||
/// First, we ask the user to choose between image picker and camera
|
return await showPickFileDialog(
|
||||||
final result = await showDialog<_ChooseImageSource>(
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) {
|
allowedMimeTypes: ["image/png", "image/jpeg", "image/gif"],
|
||||||
return AlertDialog(
|
imageMaxHeight: 10000,
|
||||||
title: Text(tr("Choose an image")),
|
imageMaxWidth: 10000,
|
||||||
actions: <Widget>[
|
|
||||||
//Gallery
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context, _ChooseImageSource.GALLERY),
|
|
||||||
child: Text(
|
|
||||||
tr("Image gallery").toUpperCase(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Camera
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context, _ChooseImageSource.CAMERA),
|
|
||||||
child: Text(
|
|
||||||
tr("Camera").toUpperCase(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result == null) return null;
|
|
||||||
return await ImagePicker().getImage(
|
|
||||||
source: result == _ChooseImageSource.CAMERA
|
|
||||||
? ImageSource.camera
|
|
||||||
: ImageSource.gallery);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a new temporary file
|
/// Generate a new temporary file
|
||||||
|
Loading…
Reference in New Issue
Block a user