Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/share_plus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ jobs:
run: ./.github/workflows/scripts/build-examples.sh windows ./lib/main.dart

windows_integration_test:
if: false # TODO: enable this if it is possible to register a mailto protocol handler on github hosted runners
runs-on: windows-latest
timeout-minutes: 30
steps:
Expand Down
35 changes: 25 additions & 10 deletions packages/share_plus/share_plus/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

// ignore_for_file: public_member_api_docs

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:share_plus/share_plus.dart';
import 'package:image_picker/image_picker.dart';

import 'image_previews.dart';
import 'utils/file_picker_win.dart'
if (dart.library.html) 'utils/file_picker_web.dart';

void main() {
runApp(const DemoApp());
Expand Down Expand Up @@ -67,15 +70,27 @@ class DemoAppState extends State<DemoApp> {
leading: const Icon(Icons.add),
title: const Text('Add image'),
onTap: () async {
final imagePicker = ImagePicker();
final pickedFile = await imagePicker.pickImage(
source: ImageSource.gallery,
);
if (pickedFile != null) {
setState(() {
imagePaths.add(pickedFile.path);
imageNames.add(pickedFile.name);
});
// Using `package:image_picker` to get image from gallery.
if (Platform.isWindows) {
// Using `package:filepicker_windows` on Windows, since `package:image_picker` is not supported.
final path = await pickFile();
if (path != null) {
setState(() {
imagePaths.add(path);
imageNames.add(path.split('\\').last);
});
}
} else {
final imagePicker = ImagePicker();
final pickedFile = await imagePicker.pickImage(
source: ImageSource.gallery,
);
if (pickedFile != null) {
setState(() {
imagePaths.add(pickedFile.path);
imageNames.add(pickedFile.name);
});
}
}
},
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// Dummy implementation for non `dart.library.io` platforms.
Future<String?> pickFile() async {
throw UnimplementedError();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:filepicker_windows/filepicker_windows.dart';

/// Picks a file from the file system on Windows & returns it's path as [String].
Future<String?> pickFile() async {
final picker = OpenFilePicker()
..filterSpecification = {'Images': '*.jpg;*.jpeg;*.png;*.gif'};
final result = picker.getFile();
return result?.path;
}
1 change: 1 addition & 0 deletions packages/share_plus/share_plus/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dependencies:
share_plus:
path: ../
image_picker: ^0.8.4
filepicker_windows: ^2.0.2

dependency_overrides:
share_plus_linux:
Expand Down
12 changes: 12 additions & 0 deletions packages/share_plus/share_plus_windows/.clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Defines the Chromium style for automatic reformatting.
# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
BasedOnStyle: Chromium
# This defaults to 'Auto'. Explicitly set it for a while, so that
# 'vector<vector<int> >' in existing files gets formatted to
# 'vector<vector<int>>'. ('Auto' means that clang-format will only use
# 'int>>' if the file already contains at least one such instance.)
Standard: Cpp11
SortIncludes: true
---
Language: ObjC
ColumnLimit: 100
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ library share_plus_windows;

import 'dart:ui';

import 'package:share_plus_windows/src/version_helper.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:share_plus_platform_interface/share_plus_platform_interface.dart';

/// The Windows implementation of SharePlatform.
/// The fallback Windows implementation of [SharePlatform], for older Windows versions.
///
class ShareWindows extends SharePlatform {
/// Register this dart class as the platform implementation for linux
/// If the modern Share UI i.e. `DataTransferManager` is not available, then use this Dart class instead of platform specific implementation.
///
static void registerWith() {
SharePlatform.instance = ShareWindows();
if (!VersionHelper.instance.isWindows10RS5OrGreater) {
SharePlatform.instance = ShareWindows();
}
}

/// Share text.
Expand Down
43 changes: 43 additions & 0 deletions packages/share_plus/share_plus_windows/lib/src/version_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'dart:io';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';

class VersionHelper {
static VersionHelper instance = VersionHelper._();

/// Whether the current OS is Windows 10 Redstone 5 or later.
/// This is used to determine whether the modern Share UI i.e. `DataTransferManager` is available or not.
///
/// References: https://en.wikipedia.org/wiki/Windows_10_version_history
/// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexa
///
bool isWindows10RS5OrGreater = false;

static const int _kWindows10RS5BuildNumber = 17763;

VersionHelper._() {
if (Platform.isWindows) {
final pointer = calloc<OSVERSIONINFOEX>();
pointer.ref
..dwOSVersionInfoSize = sizeOf<OSVERSIONINFOEX>()
..dwBuildNumber = 0
..dwMajorVersion = 0
..dwMinorVersion = 0
..dwPlatformId = 0
..szCSDVersion = ''
..wServicePackMajor = 0
..wServicePackMinor = 0
..wSuiteMask = 0
..wProductType = 0
..wReserved = 0;
final rtlGetVersion = DynamicLibrary.open('ntdll.dll').lookupFunction<
Void Function(Pointer<OSVERSIONINFOEX>),
void Function(Pointer<OSVERSIONINFOEX>)>('RtlGetVersion');
rtlGetVersion(pointer);
isWindows10RS5OrGreater =
pointer.ref.dwBuildNumber >= _kWindows10RS5BuildNumber;
calloc.free(pointer);
}
}
}
6 changes: 3 additions & 3 deletions packages/share_plus/share_plus_windows/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ dependencies:
sdk: flutter
meta: ^1.7.0
url_launcher: ^6.1.2
ffi: ^2.0.1
win32: ^2.3.8

dev_dependencies:
flutter_test:
Expand All @@ -23,8 +25,6 @@ dev_dependencies:

flutter:
plugin:
implements: share_plus
platforms:
windows:
dartPluginClass: ShareWindows
pluginClass: none
pluginClass: SharePlusWindowsPluginCApi
Original file line number Diff line number Diff line change
@@ -1,40 +1,72 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:share_plus_platform_interface/share_plus_platform_interface.dart';
import 'package:share_plus_windows/share_plus_windows.dart';
import 'package:share_plus_windows/src/version_helper.dart';
import 'package:share_plus_platform_interface/share_plus_platform_interface.dart';
import 'package:share_plus_platform_interface/method_channel/method_channel_share.dart';

import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
import 'package:url_launcher_platform_interface/link.dart';

void main() {
test('registered instance', () {
ShareWindows.registerWith();
expect(SharePlatform.instance, isA<ShareWindows>());
});
test('url encoding is correct for &', () async {
final mock = MockUrlLauncherPlatform();
UrlLauncherPlatform.instance = mock;
test(
'registered instance',
() {
ShareWindows.registerWith();
expect(SharePlatform.instance, isA<ShareWindows>());
},
skip: VersionHelper.instance.isWindows10RS5OrGreater,
);

test(
'registered instance',
() {
ShareWindows.registerWith();
expect(SharePlatform.instance, isA<MethodChannelShare>());
},
skip: !VersionHelper.instance.isWindows10RS5OrGreater,
);

// These tests are only valid on Windows versions lower than 10.0.17763.0.

test(
'url encoding is correct for &',
() async {
final mock = MockUrlLauncherPlatform();
UrlLauncherPlatform.instance = mock;

await ShareWindows().share('foo&bar', subject: 'bar&foo');
await ShareWindows().share('foo&bar', subject: 'bar&foo');

expect(mock.url, 'mailto:?subject=bar%26foo&body=foo%26bar');
});
expect(mock.url, 'mailto:?subject=bar%26foo&body=foo%26bar');
},
skip: VersionHelper.instance.isWindows10RS5OrGreater,
);

// see https://github.com/dart-lang/sdk/issues/43838#issuecomment-823551891
test('url encoding is correct for spaces', () async {
final mock = MockUrlLauncherPlatform();
UrlLauncherPlatform.instance = mock;
test(
'url encoding is correct for spaces',
() async {
final mock = MockUrlLauncherPlatform();
UrlLauncherPlatform.instance = mock;

await ShareWindows().share('foo bar', subject: 'bar foo');
await ShareWindows().share('foo bar', subject: 'bar foo');

expect(mock.url, 'mailto:?subject=bar%20foo&body=foo%20bar');
});
expect(mock.url, 'mailto:?subject=bar%20foo&body=foo%20bar');
},
skip: VersionHelper.instance.isWindows10RS5OrGreater,
);

test('throws when url_launcher can\'t launch uri', () async {
final mock = MockUrlLauncherPlatform();
mock.canLaunchMockValue = false;
UrlLauncherPlatform.instance = mock;
test(
'throws when url_launcher can\'t launch uri',
() async {
final mock = MockUrlLauncherPlatform();
mock.canLaunchMockValue = false;
UrlLauncherPlatform.instance = mock;

expect(() async => await ShareWindows().share('foo bar'), throwsException);
});
expect(
() async => await ShareWindows().share('foo bar'), throwsException);
},
skip: VersionHelper.instance.isWindows10RS5OrGreater,
);
}

class MockUrlLauncherPlatform extends UrlLauncherPlatform {
Expand Down
17 changes: 17 additions & 0 deletions packages/share_plus/share_plus_windows/windows/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
flutter/

# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates

# Visual Studio build-related files.
x64/
x86/

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
53 changes: 53 additions & 0 deletions packages/share_plus/share_plus_windows/windows/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# The Flutter tooling requires that developers have a version of Visual Studio
# installed that includes CMake 3.14 or later. You should not increase this
# version, as doing so will cause the plugin to fail to compile for some
# customers of the plugin.
cmake_minimum_required(VERSION 3.14)

# Project-level configuration.
set(PROJECT_NAME "share_plus_windows")
project(${PROJECT_NAME} LANGUAGES CXX)

# This value is used when generating builds using this plugin, so it must
# not be changed
set(PLUGIN_NAME "share_plus_windows_plugin")

# Any new source files that you add to the plugin should be added here.
list(APPEND PLUGIN_SOURCES
"share_plus_windows_plugin.cpp"
"share_plus_windows_plugin.h"
)

# Define the plugin library target. Its name must not be changed (see comment
# on PLUGIN_NAME above).
add_library(${PLUGIN_NAME} SHARED
"include/share_plus_windows/share_plus_windows_plugin_c_api.h"
"share_plus_windows_plugin_c_api.cpp"
${PLUGIN_SOURCES}
)

# Apply a standard set of build settings that are configured in the
# application-level CMakeLists.txt. This can be removed for plugins that want
# full control over build settings.
apply_standard_settings(${PLUGIN_NAME})

# Symbols are hidden by default to reduce the chance of accidental conflicts
# between plugins. This should not be removed; any symbols that should be
# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro.
set_target_properties(${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)

# Source include directories and library dependencies. Add any plugin-specific
# dependencies here.
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)

# List of absolute paths to libraries that should be bundled with the plugin.
# This list could contain prebuilt libraries, or libraries created by an
# external build triggered from this build file.
set(share_plus_windows_bundled_libraries
""
PARENT_SCOPE
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#ifndef FLUTTER_PLUGIN_SHARE_PLUS_WINDOWS_PLUGIN_C_API_H_
#define FLUTTER_PLUGIN_SHARE_PLUS_WINDOWS_PLUGIN_C_API_H_

#include <flutter_plugin_registrar.h>

#ifdef FLUTTER_PLUGIN_IMPL
#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport)
#endif

#if defined(__cplusplus)
extern "C" {
#endif

FLUTTER_PLUGIN_EXPORT void SharePlusWindowsPluginCApiRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar);

#if defined(__cplusplus)
} // extern "C"
#endif

#endif // FLUTTER_PLUGIN_SHARE_PLUS_WINDOWS_PLUGIN_C_API_H_
Loading