Skip to content

Commit cd7b935

Browse files
[image_picker] Add desktop support (flutter#3882)
(This is the combination PR for overall review.) This adds initial desktop support across all three desktop platforms: - Adds the concept of a camera delegation as discussed in flutter#102115, and adds a helper base class at the platform interface level for implementations that want to use that approach. (Sharing it simplifies both the platform implementations and the client usage.) - Adds a new app-facing and platform-interface API to check at runtime for support for a given source, to make writing code using `image_picker` in a world of dynamic camera support viable. (In particular this is critical for published packages that use `image_picker`, as they won't know in advance if a client app will have a delegate set up). - Updates the Windows implementation to: - use the new delegation base class - implement the newer `getImageFromSource` API as an opportunistic fix while changing the code (if not implemented, it would fall back to `getImage`) - use new APIs in its example. - Made macOS and Linux implementations that are clones of the Windows approach. They are both slightly simpler however, as MIME type (for Linux) and UTI (for macOS) allow creating generic "image" and "video" filters instead of having to hard-code a file extension list. Since in my opinion this level of support is sufficient to allow basic real-world use of the desktop implementations, due to having a structure in place for handling camera support, this includes endorsing the desktop implementations. It is still the case that desktop does not support image resizing, which isn't ideal, but I don't think we should block on that. It is clearly documented in the README. The desktop implementations are separate packages even though they are mostly the same right now because: - the amount of (non-example) code being duplicated is small, and - it makes it much easier for us to change platform implementations over time; e.g., recent versions of macOS support `PHPicker`, meaning we can later add native code for that to the macOS implementation, with just a fallback to the current `file_selector` wrapper. It's plausible that better native options for Windows and/or Linux may become available in the future as well. Fixes flutter#102115 Fixes flutter#102320 Fixes flutter#85100
1 parent 6565f17 commit cd7b935

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2892
-29
lines changed

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,14 @@ packages/webview_flutter/webview_flutter_wkwebview/** @cyanglaz
8383

8484
# - Linux
8585
packages/file_selector/file_selector_linux/** @cbracken
86+
packages/image_picker/image_picker_linux/** @cbracken
8687
packages/path_provider/path_provider_linux/** @cbracken
8788
packages/shared_preferences/shared_preferences_linux/** @cbracken
8889
packages/url_launcher/url_launcher_linux/** @cbracken
8990

9091
# - macOS
9192
packages/file_selector/file_selector_macos/** @cbracken
93+
packages/image_picker/image_picker_macos/** @cbracken
9294
packages/url_launcher/url_launcher_macos/** @cbracken
9395

9496
# - Windows

packages/image_picker/image_picker/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.8.8
2+
3+
* Adds initial support for Windows, macOS, and Linux.
4+
* See README for current desktop limitations.
5+
* Adds `supportsImageSource` to allow runtime checks for whether a given source
6+
is supported by the current platform's implementation.
7+
18
## 0.8.7+5
29

310
* Fixes `BuildContext` handling in example.

packages/image_picker/image_picker/README.md

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
A Flutter plugin for iOS and Android for picking images from the image library,
77
and taking new pictures with the camera.
88

9-
| | Android | iOS | Web |
10-
|-------------|---------|---------|---------------------------------|
11-
| **Support** | SDK 21+ | iOS 11+ | [See `image_picker_for_web`][1] |
9+
| | Android | iOS | Linux | macOS | Web | Windows |
10+
|-------------|---------|---------|-------|--------|---------------------------------|-------------|
11+
| **Support** | SDK 21+ | iOS 11+ | Any | 10.14+ | [See `image_picker_for_web`][1] | Windows 10+ |
1212

1313
## Installation
1414

@@ -109,6 +109,61 @@ As activities cannot communicate between tasks, the image picker activity cannot
109109
send back its eventual result to the calling activity.
110110
To work around this problem, consider using `launchMode: singleTask` instead.
111111

112+
### Windows, macOS, and Linux
113+
114+
This plugin currently has limited support for the three desktop platforms,
115+
serving as a wrapper around the [`file_selector`](https://pub.dev/packages/file_selector)
116+
plugin with appropriate file type filters set. Selection modification options,
117+
such as max width and height, are not yet supported.
118+
119+
By default, `ImageSource.camera` is not supported, since unlike on Android and
120+
iOS there is no system-provided UI for taking photos. However, the desktop
121+
implementations allow delegating to a camera handler by setting a
122+
`cameraDelegate` before using `image_picker`, such as in `main()`:
123+
124+
<?code-excerpt "readme_excerpts.dart (CameraDelegate)"?>
125+
``` dart
126+
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
127+
// ···
128+
class MyCameraDelegate extends ImagePickerCameraDelegate {
129+
@override
130+
Future<XFile?> takePhoto(
131+
{ImagePickerCameraDelegateOptions options =
132+
const ImagePickerCameraDelegateOptions()}) async {
133+
return _takeAPhoto(options.preferredCameraDevice);
134+
}
135+
136+
@override
137+
Future<XFile?> takeVideo(
138+
{ImagePickerCameraDelegateOptions options =
139+
const ImagePickerCameraDelegateOptions()}) async {
140+
return _takeAVideo(options.preferredCameraDevice);
141+
}
142+
}
143+
// ···
144+
void setUpCameraDelegate() {
145+
final ImagePickerPlatform instance = ImagePickerPlatform.instance;
146+
if (instance is CameraDelegatingImagePickerPlatform) {
147+
instance.cameraDelegate = MyCameraDelegate();
148+
}
149+
}
150+
```
151+
152+
Once you have set a `cameraDelegate`, `image_picker` calls with
153+
`ImageSource.camera` will work as normal, calling your provided delegate. We
154+
encourage the community to build packages that implement
155+
`ImagePickerCameraDelegate`, to provide options for desktop camera UI.
156+
157+
#### macOS installation
158+
159+
Since the macOS implementation uses `file_selector`, you will need to
160+
add a filesystem access
161+
[entitlement][https://docs.flutter.dev/platform-integration/macos/building#entitlements-and-the-app-sandbox]:
162+
```xml
163+
<key>com.apple.security.files.user-selected.read-only</key>
164+
<true/>
165+
```
166+
112167
### Example
113168

114169
<?code-excerpt "readme_excerpts.dart (Pick)"?>

packages/image_picker/image_picker/example/lib/main.dart

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -316,18 +316,19 @@ class _MyHomePageState extends State<MyHomePage> {
316316
child: const Icon(Icons.photo_library),
317317
),
318318
),
319-
Padding(
320-
padding: const EdgeInsets.only(top: 16.0),
321-
child: FloatingActionButton(
322-
onPressed: () {
323-
isVideo = false;
324-
_onImageButtonPressed(ImageSource.camera, context: context);
325-
},
326-
heroTag: 'image2',
327-
tooltip: 'Take a Photo',
328-
child: const Icon(Icons.camera_alt),
319+
if (_picker.supportsImageSource(ImageSource.camera))
320+
Padding(
321+
padding: const EdgeInsets.only(top: 16.0),
322+
child: FloatingActionButton(
323+
onPressed: () {
324+
isVideo = false;
325+
_onImageButtonPressed(ImageSource.camera, context: context);
326+
},
327+
heroTag: 'image2',
328+
tooltip: 'Take a Photo',
329+
child: const Icon(Icons.camera_alt),
330+
),
329331
),
330-
),
331332
Padding(
332333
padding: const EdgeInsets.only(top: 16.0),
333334
child: FloatingActionButton(
@@ -341,19 +342,20 @@ class _MyHomePageState extends State<MyHomePage> {
341342
child: const Icon(Icons.video_library),
342343
),
343344
),
344-
Padding(
345-
padding: const EdgeInsets.only(top: 16.0),
346-
child: FloatingActionButton(
347-
backgroundColor: Colors.red,
348-
onPressed: () {
349-
isVideo = true;
350-
_onImageButtonPressed(ImageSource.camera, context: context);
351-
},
352-
heroTag: 'video1',
353-
tooltip: 'Take a Video',
354-
child: const Icon(Icons.videocam),
345+
if (_picker.supportsImageSource(ImageSource.camera))
346+
Padding(
347+
padding: const EdgeInsets.only(top: 16.0),
348+
child: FloatingActionButton(
349+
backgroundColor: Colors.red,
350+
onPressed: () {
351+
isVideo = true;
352+
_onImageButtonPressed(ImageSource.camera, context: context);
353+
},
354+
heroTag: 'video1',
355+
tooltip: 'Take a Video',
356+
child: const Icon(Icons.videocam),
357+
),
355358
),
356-
),
357359
],
358360
),
359361
);

packages/image_picker/image_picker/example/lib/readme_excerpts.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,28 @@
44

55
import 'package:flutter/services.dart';
66
import 'package:image_picker/image_picker.dart';
7+
// #docregion CameraDelegate
8+
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
9+
// #enddocregion CameraDelegate
10+
11+
/// Example of a camera delegate
12+
// #docregion CameraDelegate
13+
class MyCameraDelegate extends ImagePickerCameraDelegate {
14+
@override
15+
Future<XFile?> takePhoto(
16+
{ImagePickerCameraDelegateOptions options =
17+
const ImagePickerCameraDelegateOptions()}) async {
18+
return _takeAPhoto(options.preferredCameraDevice);
19+
}
20+
21+
@override
22+
Future<XFile?> takeVideo(
23+
{ImagePickerCameraDelegateOptions options =
24+
const ImagePickerCameraDelegateOptions()}) async {
25+
return _takeAVideo(options.preferredCameraDevice);
26+
}
27+
}
28+
// #enddocregion CameraDelegate
729

830
/// Example function for README demonstration of various pick* calls.
931
Future<List<XFile?>> readmePickExample() async {
@@ -49,6 +71,20 @@ Future<void> getLostData() async {
4971
}
5072
// #enddocregion LostData
5173

74+
/// Example of camera delegate setup.
75+
// #docregion CameraDelegate
76+
void setUpCameraDelegate() {
77+
final ImagePickerPlatform instance = ImagePickerPlatform.instance;
78+
if (instance is CameraDelegatingImagePickerPlatform) {
79+
instance.cameraDelegate = MyCameraDelegate();
80+
}
81+
}
82+
// #enddocregion CameraDelegate
83+
5284
// Stubs for the getLostData function.
5385
void _handleLostFiles(List<XFile> file) {}
5486
void _handleError(PlatformException? exception) {}
87+
88+
// Stubs for MyCameraDelegate.
89+
Future<XFile?> _takeAPhoto(CameraDevice device) async => null;
90+
Future<XFile?> _takeAVideo(CameraDevice device) async => null;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
flutter/ephemeral
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Project-level configuration.
2+
cmake_minimum_required(VERSION 3.10)
3+
project(runner LANGUAGES CXX)
4+
5+
# The name of the executable created for the application. Change this to change
6+
# the on-disk name of your application.
7+
set(BINARY_NAME "image_picker_example")
8+
# The unique GTK application identifier for this application. See:
9+
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
10+
set(APPLICATION_ID "dev.flutter.plugins.image_picker_example")
11+
12+
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
13+
# versions of CMake.
14+
cmake_policy(SET CMP0063 NEW)
15+
16+
# Load bundled libraries from the lib/ directory relative to the binary.
17+
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
18+
19+
# Root filesystem for cross-building.
20+
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
21+
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
22+
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
23+
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
24+
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
25+
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
26+
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
27+
endif()
28+
29+
# Define build configuration options.
30+
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
31+
set(CMAKE_BUILD_TYPE "Debug" CACHE
32+
STRING "Flutter build mode" FORCE)
33+
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
34+
"Debug" "Profile" "Release")
35+
endif()
36+
37+
# Compilation settings that should be applied to most targets.
38+
#
39+
# Be cautious about adding new options here, as plugins use this function by
40+
# default. In most cases, you should add new options to specific targets instead
41+
# of modifying this function.
42+
function(APPLY_STANDARD_SETTINGS TARGET)
43+
target_compile_features(${TARGET} PUBLIC cxx_std_14)
44+
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
45+
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
46+
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
47+
endfunction()
48+
49+
# Flutter library and tool build rules.
50+
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
51+
add_subdirectory(${FLUTTER_MANAGED_DIR})
52+
53+
# System-level dependencies.
54+
find_package(PkgConfig REQUIRED)
55+
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
56+
57+
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
58+
59+
# Define the application target. To change its name, change BINARY_NAME above,
60+
# not the value here, or `flutter run` will no longer work.
61+
#
62+
# Any new source files that you add to the application should be added here.
63+
add_executable(${BINARY_NAME}
64+
"main.cc"
65+
"my_application.cc"
66+
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
67+
)
68+
69+
# Apply the standard set of build settings. This can be removed for applications
70+
# that need different build settings.
71+
apply_standard_settings(${BINARY_NAME})
72+
73+
# Add dependency libraries. Add any application-specific dependencies here.
74+
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
75+
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
76+
77+
# Run the Flutter tool portions of the build. This must not be removed.
78+
add_dependencies(${BINARY_NAME} flutter_assemble)
79+
80+
# Only the install-generated bundle's copy of the executable will launch
81+
# correctly, since the resources must in the right relative locations. To avoid
82+
# people trying to run the unbundled copy, put it in a subdirectory instead of
83+
# the default top-level location.
84+
set_target_properties(${BINARY_NAME}
85+
PROPERTIES
86+
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
87+
)
88+
89+
# Generated plugin build rules, which manage building the plugins and adding
90+
# them to the application.
91+
include(flutter/generated_plugins.cmake)
92+
93+
94+
# === Installation ===
95+
# By default, "installing" just makes a relocatable bundle in the build
96+
# directory.
97+
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
98+
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
99+
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
100+
endif()
101+
102+
# Start with a clean build bundle directory every time.
103+
install(CODE "
104+
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
105+
" COMPONENT Runtime)
106+
107+
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
108+
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
109+
110+
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
111+
COMPONENT Runtime)
112+
113+
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
114+
COMPONENT Runtime)
115+
116+
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
117+
COMPONENT Runtime)
118+
119+
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
120+
install(FILES "${bundled_library}"
121+
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
122+
COMPONENT Runtime)
123+
endforeach(bundled_library)
124+
125+
# Fully re-copy the assets directory on each build to avoid having stale files
126+
# from a previous install.
127+
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
128+
install(CODE "
129+
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
130+
" COMPONENT Runtime)
131+
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
132+
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
133+
134+
# Install the AOT library on non-Debug builds only.
135+
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
136+
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
137+
COMPONENT Runtime)
138+
endif()

0 commit comments

Comments
 (0)