mirror of
https://github.com/flutter/packages.git
synced 2025-06-30 23:03:11 +08:00
[image_picker] Update to 1.0 (#4285)
The plugin now covers all of our supported platforms, most highly-requested features have been addressed, and the API has been stable for quite some time (in that no breaking changes have been required). Given that, a 1.0 version number reflects the current state of the package than a 0.x version. As part of the breaking change, the `get*` methods that were deprecated several years ago have now been removed.
This commit is contained in:
@ -1,6 +1,16 @@
|
||||
## 1.0.0
|
||||
|
||||
* **BREAKING CHANGE**: Removes the deprecated `get*` methods. Clients who have
|
||||
not already done so will need to migrate to the `pick*` versions that use
|
||||
`XFile` rather than `PickedFile` for return values.
|
||||
* As this is the only change, we encourage authors of published packages
|
||||
that depend on `image_picker` to consider using a constraint of
|
||||
`'>=0.8.9 <2.0.0'` rather than `^1.0.0` when updating dependencies, to
|
||||
avoid conflicts with packages that have not yet updated.
|
||||
|
||||
## 0.8.9
|
||||
|
||||
* Adds `getMedia` and `getMultipleMedia` methods.
|
||||
* Adds `pickMedia` and `pickMultipleMedia` methods.
|
||||
|
||||
## 0.8.8
|
||||
|
||||
|
@ -186,15 +186,13 @@ final XFile? media = await picker.pickMedia();
|
||||
final List<XFile> medias = await picker.pickMultipleMedia();
|
||||
```
|
||||
|
||||
## Migrating to 0.8.2+
|
||||
## Migrating to 1.0
|
||||
|
||||
Starting with version **0.8.2** of the image_picker plugin, new methods have
|
||||
been added for picking files that return `XFile` instances (from the
|
||||
Starting with version 0.8.2 of the image_picker plugin, new methods were
|
||||
added that return `XFile` instances (from the
|
||||
[cross_file](https://pub.dev/packages/cross_file) package) rather than the
|
||||
plugin's own `PickedFile` instances. While the previous methods still exist, it
|
||||
is already recommended to start migrating over to their new equivalents.
|
||||
Eventually, `PickedFile` and the methods that return instances of it will be
|
||||
deprecated and removed.
|
||||
plugin's own `PickedFile` instances. The previous methods were supported through
|
||||
0.8.9, and removed in 1.0.0.
|
||||
|
||||
#### Call the new methods
|
||||
|
||||
|
@ -25,150 +25,10 @@ class ImagePicker {
|
||||
@visibleForTesting
|
||||
static ImagePickerPlatform get platform => ImagePickerPlatform.instance;
|
||||
|
||||
/// Returns a [PickedFile] object wrapping the image that was picked.
|
||||
///
|
||||
/// The returned [PickedFile] is intended to be used within a single app session. Do not save the file path and use it across sessions.
|
||||
///
|
||||
/// The `source` argument controls where the image comes from. This can
|
||||
/// be either [ImageSource.camera] or [ImageSource.gallery].
|
||||
///
|
||||
/// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used
|
||||
/// in addition to a size modification, of which the usage is explained below.
|
||||
///
|
||||
/// If specified, the image will be at most `maxWidth` wide and
|
||||
/// `maxHeight` tall. Otherwise the image will be returned at it's
|
||||
/// original width and height.
|
||||
/// The `imageQuality` argument modifies the quality of the image, ranging from 0-100
|
||||
/// where 100 is the original/max quality. If `imageQuality` is null, the image with
|
||||
/// the original quality will be returned. Compression is only supported for certain
|
||||
/// image types such as JPEG and on Android PNG and WebP, too. If compression is not supported for the image that is picked,
|
||||
/// a warning message will be logged.
|
||||
///
|
||||
/// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
|
||||
/// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
|
||||
/// Defaults to [CameraDevice.rear]. Note that Android has no documented parameter for an intent to specify if
|
||||
/// the front or rear camera should be opened, this function is not guaranteed
|
||||
/// to work on an Android device.
|
||||
///
|
||||
/// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost
|
||||
/// in this call. You can then call [getLostData] when your app relaunches to retrieve the lost data.
|
||||
///
|
||||
/// See also [getMultiImage] to allow users to select multiple images at once.
|
||||
///
|
||||
/// The method could throw [PlatformException] if the app does not have permission to access
|
||||
/// the camera or photos gallery, no camera is available, plugin is already in use,
|
||||
/// temporary file could not be created (iOS only), plugin activity could not
|
||||
/// be allocated (Android only) or due to an unknown error.
|
||||
@Deprecated('Switch to using pickImage instead')
|
||||
Future<PickedFile?> getImage({
|
||||
required ImageSource source,
|
||||
double? maxWidth,
|
||||
double? maxHeight,
|
||||
int? imageQuality,
|
||||
CameraDevice preferredCameraDevice = CameraDevice.rear,
|
||||
}) {
|
||||
return platform.pickImage(
|
||||
source: source,
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: maxHeight,
|
||||
imageQuality: imageQuality,
|
||||
preferredCameraDevice: preferredCameraDevice,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a [List<PickedFile>] object wrapping the images that were picked.
|
||||
///
|
||||
/// The returned [List<PickedFile>] is intended to be used within a single app session. Do not save the file path and use it across sessions.
|
||||
///
|
||||
/// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used
|
||||
/// in addition to a size modification, of which the usage is explained below.
|
||||
///
|
||||
/// This method is not supported in iOS versions lower than 14.
|
||||
///
|
||||
/// If specified, the images will be at most `maxWidth` wide and
|
||||
/// `maxHeight` tall. Otherwise the images will be returned at it's
|
||||
/// original width and height.
|
||||
/// The `imageQuality` argument modifies the quality of the images, ranging from 0-100
|
||||
/// where 100 is the original/max quality. If `imageQuality` is null, the images with
|
||||
/// the original quality will be returned. Compression is only supported for certain
|
||||
/// image types such as JPEG and on Android PNG and WebP, too. If compression is not supported for the image that is picked,
|
||||
/// a warning message will be logged.
|
||||
///
|
||||
/// The method could throw [PlatformException] if the app does not have permission to access
|
||||
/// the camera or photos gallery, no camera is available, plugin is already in use,
|
||||
/// temporary file could not be created (iOS only), plugin activity could not
|
||||
/// be allocated (Android only) or due to an unknown error.
|
||||
///
|
||||
/// See also [getImage] to allow users to only pick a single image.
|
||||
@Deprecated('Switch to using pickMultiImage instead')
|
||||
Future<List<PickedFile>?> getMultiImage({
|
||||
double? maxWidth,
|
||||
double? maxHeight,
|
||||
int? imageQuality,
|
||||
}) {
|
||||
return platform.pickMultiImage(
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: maxHeight,
|
||||
imageQuality: imageQuality,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a [PickedFile] object wrapping the video that was picked.
|
||||
///
|
||||
/// The returned [PickedFile] is intended to be used within a single app session. Do not save the file path and use it across sessions.
|
||||
///
|
||||
/// The [source] argument controls where the video comes from. This can
|
||||
/// be either [ImageSource.camera] or [ImageSource.gallery].
|
||||
///
|
||||
/// The [maxDuration] argument specifies the maximum duration of the captured video. If no [maxDuration] is specified,
|
||||
/// the maximum duration will be infinite.
|
||||
///
|
||||
/// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
|
||||
/// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
|
||||
/// Defaults to [CameraDevice.rear].
|
||||
///
|
||||
/// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost
|
||||
/// in this call. You can then call [getLostData] when your app relaunches to retrieve the lost data.
|
||||
///
|
||||
/// The method could throw [PlatformException] if the app does not have permission to access
|
||||
/// the camera or photos gallery, no camera is available, plugin is already in use,
|
||||
/// temporary file could not be created and video could not be cached (iOS only),
|
||||
/// plugin activity could not be allocated (Android only) or due to an unknown error.
|
||||
///
|
||||
@Deprecated('Switch to using pickVideo instead')
|
||||
Future<PickedFile?> getVideo({
|
||||
required ImageSource source,
|
||||
CameraDevice preferredCameraDevice = CameraDevice.rear,
|
||||
Duration? maxDuration,
|
||||
}) {
|
||||
return platform.pickVideo(
|
||||
source: source,
|
||||
preferredCameraDevice: preferredCameraDevice,
|
||||
maxDuration: maxDuration,
|
||||
);
|
||||
}
|
||||
|
||||
/// Retrieve the lost [PickedFile] when [selectImage] or [selectVideo] failed because the MainActivity is destroyed. (Android only)
|
||||
///
|
||||
/// Image or video can be lost if the MainActivity is destroyed. And there is no guarantee that the MainActivity is always alive.
|
||||
/// Call this method to retrieve the lost data and process the data according to your app's business logic.
|
||||
///
|
||||
/// Returns a [LostData] object if successfully retrieved the lost data. The [LostData] object can represent either a
|
||||
/// successful image/video selection, or a failure.
|
||||
///
|
||||
/// Calling this on a non-Android platform will throw [UnimplementedError] exception.
|
||||
///
|
||||
/// See also:
|
||||
/// * [LostData], for what's included in the response.
|
||||
/// * [Android Activity Lifecycle](https://developer.android.com/reference/android/app/Activity.html), for more information on MainActivity destruction.
|
||||
@Deprecated('Switch to using retrieveLostData instead')
|
||||
Future<LostData> getLostData() {
|
||||
return platform.retrieveLostData();
|
||||
}
|
||||
|
||||
/// Returns an [XFile] object wrapping the image that was picked.
|
||||
///
|
||||
/// The returned [XFile] is intended to be used within a single app session. Do not save the file path and use it across sessions.
|
||||
/// The returned [XFile] is intended to be used within a single app session.
|
||||
/// Do not save the file path and use it across sessions.
|
||||
///
|
||||
/// The `source` argument controls where the image comes from. This can
|
||||
/// be either [ImageSource.camera] or [ImageSource.gallery].
|
||||
@ -234,7 +94,8 @@ class ImagePicker {
|
||||
|
||||
/// Returns a [List<XFile>] object wrapping the images that were picked.
|
||||
///
|
||||
/// The returned [List<XFile>] is intended to be used within a single app session. Do not save the file path and use it across sessions.
|
||||
/// The returned [List<XFile>] is intended to be used within a single app session.
|
||||
/// Do not save the file path and use it across sessions.
|
||||
///
|
||||
/// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used
|
||||
/// in addition to a size modification, of which the usage is explained below.
|
||||
@ -284,7 +145,8 @@ class ImagePicker {
|
||||
}
|
||||
|
||||
/// Returns an [XFile] of the image or video that was picked.
|
||||
/// The image or videos can only come from the gallery.
|
||||
///
|
||||
/// The image or video can only come from the gallery.
|
||||
///
|
||||
/// The returned [XFile] is intended to be used within a single app session.
|
||||
/// Do not save the file path and use it across sessions.
|
||||
@ -339,6 +201,7 @@ class ImagePicker {
|
||||
}
|
||||
|
||||
/// Returns a [List<XFile>] with the images and/or videos that were picked.
|
||||
///
|
||||
/// The images and videos come from the gallery.
|
||||
///
|
||||
/// The returned [List<XFile>] is intended to be used within a single app session.
|
||||
|
@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image
|
||||
library, and taking new pictures with the camera.
|
||||
repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker
|
||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
|
||||
version: 0.8.9
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <4.0.0"
|
||||
|
@ -1,285 +0,0 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
|
||||
// This file preserves the tests for the deprecated methods as they were before
|
||||
// the migration. See image_picker_test.dart for the current tests.
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
import 'image_picker_test.mocks.dart' as base_mock;
|
||||
|
||||
// Add the mixin to make the platform interface accept the mock.
|
||||
class MockImagePickerPlatform extends base_mock.MockImagePickerPlatform
|
||||
with MockPlatformInterfaceMixin {}
|
||||
|
||||
void main() {
|
||||
group('ImagePicker', () {
|
||||
late MockImagePickerPlatform mockPlatform;
|
||||
|
||||
setUp(() {
|
||||
mockPlatform = MockImagePickerPlatform();
|
||||
ImagePickerPlatform.instance = mockPlatform;
|
||||
});
|
||||
|
||||
group('#Single image/video', () {
|
||||
setUp(() {
|
||||
when(mockPlatform.pickImage(
|
||||
source: anyNamed('source'),
|
||||
maxWidth: anyNamed('maxWidth'),
|
||||
maxHeight: anyNamed('maxHeight'),
|
||||
imageQuality: anyNamed('imageQuality'),
|
||||
preferredCameraDevice: anyNamed('preferredCameraDevice')))
|
||||
.thenAnswer((Invocation _) async => null);
|
||||
});
|
||||
|
||||
group('#pickImage', () {
|
||||
test('passes the image source argument correctly', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
await picker.getImage(source: ImageSource.camera);
|
||||
await picker.getImage(source: ImageSource.gallery);
|
||||
|
||||
verifyInOrder(<Object>[
|
||||
mockPlatform.pickImage(source: ImageSource.camera),
|
||||
mockPlatform.pickImage(source: ImageSource.gallery),
|
||||
]);
|
||||
});
|
||||
|
||||
test('passes the width and height arguments correctly', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
await picker.getImage(source: ImageSource.camera);
|
||||
await picker.getImage(
|
||||
source: ImageSource.camera,
|
||||
maxWidth: 10.0,
|
||||
);
|
||||
await picker.getImage(
|
||||
source: ImageSource.camera,
|
||||
maxHeight: 10.0,
|
||||
);
|
||||
await picker.getImage(
|
||||
source: ImageSource.camera,
|
||||
maxWidth: 10.0,
|
||||
maxHeight: 20.0,
|
||||
);
|
||||
await picker.getImage(
|
||||
source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70);
|
||||
await picker.getImage(
|
||||
source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70);
|
||||
await picker.getImage(
|
||||
source: ImageSource.camera,
|
||||
maxWidth: 10.0,
|
||||
maxHeight: 20.0,
|
||||
imageQuality: 70);
|
||||
|
||||
verifyInOrder(<Object>[
|
||||
mockPlatform.pickImage(source: ImageSource.camera),
|
||||
mockPlatform.pickImage(source: ImageSource.camera, maxWidth: 10.0),
|
||||
mockPlatform.pickImage(source: ImageSource.camera, maxHeight: 10.0),
|
||||
mockPlatform.pickImage(
|
||||
source: ImageSource.camera,
|
||||
maxWidth: 10.0,
|
||||
maxHeight: 20.0,
|
||||
),
|
||||
mockPlatform.pickImage(
|
||||
source: ImageSource.camera,
|
||||
maxWidth: 10.0,
|
||||
imageQuality: 70,
|
||||
),
|
||||
mockPlatform.pickImage(
|
||||
source: ImageSource.camera,
|
||||
maxHeight: 10.0,
|
||||
imageQuality: 70,
|
||||
),
|
||||
mockPlatform.pickImage(
|
||||
source: ImageSource.camera,
|
||||
maxWidth: 10.0,
|
||||
maxHeight: 20.0,
|
||||
imageQuality: 70,
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
test('handles a null image file response gracefully', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
|
||||
expect(await picker.getImage(source: ImageSource.gallery), isNull);
|
||||
expect(await picker.getImage(source: ImageSource.camera), isNull);
|
||||
});
|
||||
|
||||
test('camera position defaults to back', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
await picker.getImage(source: ImageSource.camera);
|
||||
|
||||
verify(mockPlatform.pickImage(source: ImageSource.camera));
|
||||
});
|
||||
|
||||
test('camera position can set to front', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
await picker.getImage(
|
||||
source: ImageSource.camera,
|
||||
preferredCameraDevice: CameraDevice.front);
|
||||
|
||||
verify(mockPlatform.pickImage(
|
||||
source: ImageSource.camera,
|
||||
preferredCameraDevice: CameraDevice.front));
|
||||
});
|
||||
});
|
||||
|
||||
group('#pickVideo', () {
|
||||
setUp(() {
|
||||
when(mockPlatform.pickVideo(
|
||||
source: anyNamed('source'),
|
||||
preferredCameraDevice: anyNamed('preferredCameraDevice'),
|
||||
maxDuration: anyNamed('maxDuration')))
|
||||
.thenAnswer((Invocation _) async => null);
|
||||
});
|
||||
|
||||
test('passes the image source argument correctly', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
await picker.getVideo(source: ImageSource.camera);
|
||||
await picker.getVideo(source: ImageSource.gallery);
|
||||
|
||||
verifyInOrder(<Object>[
|
||||
mockPlatform.pickVideo(source: ImageSource.camera),
|
||||
mockPlatform.pickVideo(source: ImageSource.gallery),
|
||||
]);
|
||||
});
|
||||
|
||||
test('passes the duration argument correctly', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
await picker.getVideo(source: ImageSource.camera);
|
||||
await picker.getVideo(
|
||||
source: ImageSource.camera,
|
||||
maxDuration: const Duration(seconds: 10));
|
||||
|
||||
verifyInOrder(<Object>[
|
||||
mockPlatform.pickVideo(source: ImageSource.camera),
|
||||
mockPlatform.pickVideo(
|
||||
source: ImageSource.camera,
|
||||
maxDuration: const Duration(seconds: 10),
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
test('handles a null video file response gracefully', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
|
||||
expect(await picker.getVideo(source: ImageSource.gallery), isNull);
|
||||
expect(await picker.getVideo(source: ImageSource.camera), isNull);
|
||||
});
|
||||
|
||||
test('camera position defaults to back', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
await picker.getVideo(source: ImageSource.camera);
|
||||
|
||||
verify(mockPlatform.pickVideo(source: ImageSource.camera));
|
||||
});
|
||||
|
||||
test('camera position can set to front', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
await picker.getVideo(
|
||||
source: ImageSource.camera,
|
||||
preferredCameraDevice: CameraDevice.front);
|
||||
|
||||
verify(mockPlatform.pickVideo(
|
||||
source: ImageSource.camera,
|
||||
preferredCameraDevice: CameraDevice.front));
|
||||
});
|
||||
});
|
||||
|
||||
group('#retrieveLostData', () {
|
||||
test('retrieveLostData get success response', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
when(mockPlatform.retrieveLostData()).thenAnswer(
|
||||
(Invocation _) async => LostData(
|
||||
file: PickedFile('/example/path'), type: RetrieveType.image));
|
||||
|
||||
final LostData response = await picker.getLostData();
|
||||
|
||||
expect(response.type, RetrieveType.image);
|
||||
expect(response.file!.path, '/example/path');
|
||||
});
|
||||
|
||||
test('retrieveLostData get error response', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
when(mockPlatform.retrieveLostData()).thenAnswer(
|
||||
(Invocation _) async => LostData(
|
||||
exception: PlatformException(
|
||||
code: 'test_error_code', message: 'test_error_message'),
|
||||
type: RetrieveType.video));
|
||||
|
||||
final LostData response = await picker.getLostData();
|
||||
|
||||
expect(response.type, RetrieveType.video);
|
||||
expect(response.exception!.code, 'test_error_code');
|
||||
expect(response.exception!.message, 'test_error_message');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('Multi images', () {
|
||||
setUp(() {
|
||||
when(mockPlatform.pickMultiImage(
|
||||
maxWidth: anyNamed('maxWidth'),
|
||||
maxHeight: anyNamed('maxHeight'),
|
||||
imageQuality: anyNamed('imageQuality')))
|
||||
.thenAnswer((Invocation _) async => null);
|
||||
});
|
||||
|
||||
group('#pickMultiImage', () {
|
||||
test('passes the width and height arguments correctly', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
await picker.getMultiImage();
|
||||
await picker.getMultiImage(
|
||||
maxWidth: 10.0,
|
||||
);
|
||||
await picker.getMultiImage(
|
||||
maxHeight: 10.0,
|
||||
);
|
||||
await picker.getMultiImage(
|
||||
maxWidth: 10.0,
|
||||
maxHeight: 20.0,
|
||||
);
|
||||
await picker.getMultiImage(
|
||||
maxWidth: 10.0,
|
||||
imageQuality: 70,
|
||||
);
|
||||
await picker.getMultiImage(
|
||||
maxHeight: 10.0,
|
||||
imageQuality: 70,
|
||||
);
|
||||
await picker.getMultiImage(
|
||||
maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70);
|
||||
|
||||
verifyInOrder(<Object>[
|
||||
mockPlatform.pickMultiImage(),
|
||||
mockPlatform.pickMultiImage(maxWidth: 10.0),
|
||||
mockPlatform.pickMultiImage(maxHeight: 10.0),
|
||||
mockPlatform.pickMultiImage(maxWidth: 10.0, maxHeight: 20.0),
|
||||
mockPlatform.pickMultiImage(maxWidth: 10.0, imageQuality: 70),
|
||||
mockPlatform.pickMultiImage(maxHeight: 10.0, imageQuality: 70),
|
||||
mockPlatform.pickMultiImage(
|
||||
maxWidth: 10.0,
|
||||
maxHeight: 20.0,
|
||||
imageQuality: 70,
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
test('handles a null image file response gracefully', () async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
|
||||
expect(await picker.getMultiImage(), isNull);
|
||||
expect(await picker.getMultiImage(), isNull);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user