@ -1,22 +1,23 @@ |
|||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
|
||||
|
<?xml version='1.0' encoding='utf-8'?> |
||||
<widget id="com.ionicframework.openeventsplatformapp652778" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"> |
<widget id="com.ionicframework.openeventsplatformapp652778" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"> |
||||
<name>openEventsPlatformApp</name> |
|
||||
<description> |
|
||||
|
<name>openEventsPlatformApp</name> |
||||
|
<description> |
||||
An Ionic Framework and Cordova project. |
An Ionic Framework and Cordova project. |
||||
</description> |
</description> |
||||
<author email="you@example.com" href="http://example.com/"> |
|
||||
|
<author email="you@example.com" href="http://example.com/"> |
||||
Your Name Here |
Your Name Here |
||||
</author> |
</author> |
||||
<content src="index.html"/> |
|
||||
<access origin="*"/> |
|
||||
<preference name="webviewbounce" value="false"/> |
|
||||
<preference name="UIWebViewBounce" value="false"/> |
|
||||
<preference name="DisallowOverscroll" value="true"/> |
|
||||
<preference name="SplashScreenDelay" value="2000"/> |
|
||||
<preference name="FadeSplashScreenDuration" value="2000"/> |
|
||||
<preference name="android-minSdkVersion" value="16"/> |
|
||||
<preference name="BackupWebStorage" value="none"/> |
|
||||
<feature name="StatusBar"> |
|
||||
<param name="ios-package" value="CDVStatusBar" onload="true"/> |
|
||||
</feature> |
|
||||
</widget> |
|
||||
|
<content src="index.html" /> |
||||
|
<access origin="*" /> |
||||
|
<preference name="webviewbounce" value="false" /> |
||||
|
<preference name="UIWebViewBounce" value="false" /> |
||||
|
<preference name="DisallowOverscroll" value="true" /> |
||||
|
<preference name="SplashScreenDelay" value="2000" /> |
||||
|
<preference name="FadeSplashScreenDuration" value="2000" /> |
||||
|
<preference name="android-minSdkVersion" value="16" /> |
||||
|
<preference name="BackupWebStorage" value="none" /> |
||||
|
<feature name="StatusBar"> |
||||
|
<param name="ios-package" onload="true" value="CDVStatusBar" /> |
||||
|
</feature> |
||||
|
<plugin name="cordova-plugin-x-socialsharing" spec="~5.1.6" /> |
||||
|
</widget> |
@ -0,0 +1,33 @@ |
|||||
|
{ |
||||
|
"prepare_queue": { |
||||
|
"installed": [], |
||||
|
"uninstalled": [] |
||||
|
}, |
||||
|
"config_munge": { |
||||
|
"files": {} |
||||
|
}, |
||||
|
"installed_plugins": { |
||||
|
"cordova-plugin-console": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.openeventsplatformapp652778" |
||||
|
}, |
||||
|
"cordova-plugin-device": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.openeventsplatformapp652778" |
||||
|
}, |
||||
|
"cordova-plugin-splashscreen": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.openeventsplatformapp652778" |
||||
|
}, |
||||
|
"cordova-plugin-statusbar": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.openeventsplatformapp652778" |
||||
|
}, |
||||
|
"cordova-plugin-whitelist": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.openeventsplatformapp652778" |
||||
|
}, |
||||
|
"cordova-plugin-x-socialsharing": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.openeventsplatformapp652778" |
||||
|
}, |
||||
|
"ionic-plugin-keyboard": { |
||||
|
"PACKAGE_NAME": "com.ionicframework.openeventsplatformapp652778" |
||||
|
} |
||||
|
}, |
||||
|
"dependent_plugins": {} |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
The MIT License (MIT) |
||||
|
|
||||
|
Copyright (c) 2014-2016 Eddy Verbruggen |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
of this software and associated documentation files (the "Software"), to deal |
||||
|
in the Software without restriction, including without limitation the rights |
||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
copies of the Software, and to permit persons to whom the Software is |
||||
|
furnished to do so, subject to the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice shall be included in all |
||||
|
copies or substantial portions of the Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
|
SOFTWARE. |
@ -0,0 +1,531 @@ |
|||||
|
# PhoneGap Social Sharing plugin for Android, iOS and Windows Phone |
||||
|
|
||||
|
by [@EddyVerbruggen](http://www.twitter.com/eddyverbruggen), [read my blog about this plugin](http://www.x-services.nl/phonegap-share-plugin-facebook-twitter-social-media/754) |
||||
|
|
||||
|
[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=eddyverbruggen%40gmail%2ecom&lc=US&item_name=cordova%2dplugin%2dsocialsharing¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHosted) |
||||
|
Every now and then kind folks ask me how they can give me all their money. So if you want to contribute to my pension fund, then please go ahead :) |
||||
|
|
||||
|
<table width="100%"> |
||||
|
<tr> |
||||
|
<td width="100"><a href="http://plugins.telerik.com/plugin/socialsharing"><img src="http://www.x-services.nl/github-images/telerik-verified-plugins-marketplace.png" width="97px" height="71px" alt="Marketplace logo"/></a></td> |
||||
|
<td>For a quick demo app and easy code samples, check out the plugin page at the Verified Plugins Marketplace: http://plugins.telerik.com/plugin/socialsharing</td> |
||||
|
</tr> |
||||
|
</table> |
||||
|
|
||||
|
## 0. Index |
||||
|
|
||||
|
1. [Description](#1-description) |
||||
|
2. [Screenshots](#2-screenshots) |
||||
|
3. [Installation](#3-installation) |
||||
|
3. [Automatically (CLI / Plugman)](#automatically-cli--plugman) |
||||
|
3. [Manually](#manually) |
||||
|
3. [PhoneGap Build](#phonegap-build) |
||||
|
4. Usage |
||||
|
4. [iOS and Android](#4a-usage-on-ios-and-android) |
||||
|
4. [Windows Phone](#4b-usage-on-windows-phone) |
||||
|
4. [Share-popover on iPad](#4c-share-popover-on-ipad) |
||||
|
4. [Whitelisting on iOS 9](#4d-whitelisting-on-ios-9) |
||||
|
5. [Credits](#5-credits) |
||||
|
6. [License](#6-license) |
||||
|
|
||||
|
## 1. Description |
||||
|
|
||||
|
This plugin allows you to use the native sharing window of your mobile device. |
||||
|
|
||||
|
* Works on Android, version 2.3.3 and higher (probably 2.2 as well). |
||||
|
* Works on iOS6 and up. |
||||
|
* Works on Windows Phone 8 since v4.0 of this plugin (maybe even WP7, but I have no such testdevice). |
||||
|
* Share text, a link, a images (or other files like pdf or ics). Subject is also supported, when the receiving app supports it. |
||||
|
* Supports sharing files from the internet, the local filesystem, or from the www folder. |
||||
|
* You can skip the sharing dialog and directly share to Twitter, Facebook, or other apps. |
||||
|
* Compatible with [Cordova Plugman](https://github.com/apache/cordova-plugman). |
||||
|
* Officially supported by [PhoneGap Build](https://build.phonegap.com/plugins). |
||||
|
|
||||
|
## 2. Screenshots |
||||
|
iOS 7 (iPhone) |
||||
|
|
||||
|
![ScreenShot](https://raw.githubusercontent.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/master/screenshots/screenshot-ios7-share.png) |
||||
|
|
||||
|
Sharing options are based on what has been setup in the device settings |
||||
|
|
||||
|
![ScreenShot](https://raw.githubusercontent.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/master/screenshots/screenshots-ios7-shareconfig.png) |
||||
|
|
||||
|
iOS 7 (iPad) - a popup like this requires [a little more effort](#4c-share-popover-on-ipad) |
||||
|
|
||||
|
![ScreenShot](https://raw.githubusercontent.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/master/screenshots/screenshot-ios7-ipad-share.png) |
||||
|
|
||||
|
iOS 6 (iPhone) |
||||
|
|
||||
|
![ScreenShot](https://raw.githubusercontent.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/master/screenshots/screenshot-ios6-share.png) |
||||
|
|
||||
|
Android |
||||
|
|
||||
|
![ScreenShot](https://raw.githubusercontent.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/master/screenshots/screenshot-android-share.png) |
||||
|
|
||||
|
Windows Phone 8 |
||||
|
|
||||
|
![ScreenShot](https://raw.githubusercontent.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/master/screenshots/screenshot-wp8-share.jpg) |
||||
|
|
||||
|
#### Alternative ShareSheet (iOS only, using the [Cordova ActionSheet plugin](https://github.com/EddyVerbruggen/cordova-plugin-actionsheet)) |
||||
|
|
||||
|
![ScreenShot](https://raw.githubusercontent.com/EddyVerbruggen/cordova-plugin-actionsheet/master/screenshots/ios/ios-share.png) |
||||
|
|
||||
|
## 3. Installation |
||||
|
|
||||
|
### Automatically (CLI / Plugman) |
||||
|
SocialSharing is compatible with [Cordova Plugman](https://github.com/apache/cordova-plugman), compatible with [PhoneGap 3.0 CLI](http://docs.phonegap.com/en/3.0.0/guide_cli_index.md.html#The%20Command-line%20Interface_add_features), here's how it works with the CLI: |
||||
|
|
||||
|
``` |
||||
|
$ phonegap local plugin add https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin.git |
||||
|
``` |
||||
|
|
||||
|
or with Cordova CLI, from npm: |
||||
|
``` |
||||
|
$ cordova plugin add cordova-plugin-x-socialsharing |
||||
|
$ cordova prepare |
||||
|
``` |
||||
|
|
||||
|
SocialSharing.js is brought in automatically. There is no need to change or add anything in your html. |
||||
|
|
||||
|
### Manually |
||||
|
|
||||
|
1\. Add the following xml to all the `config.xml` files you can find: |
||||
|
```xml |
||||
|
<!-- for iOS --> |
||||
|
<feature name="SocialSharing"> |
||||
|
<param name="ios-package" value="SocialSharing" /> |
||||
|
</feature> |
||||
|
``` |
||||
|
```xml |
||||
|
<!-- for Android (you will find one in res/xml) --> |
||||
|
<feature name="SocialSharing"> |
||||
|
<param name="android-package" value="nl.xservices.plugins.SocialSharing" /> |
||||
|
</feature> |
||||
|
``` |
||||
|
```xml |
||||
|
<!-- for Windows Phone --> |
||||
|
<feature name="SocialSharing"> |
||||
|
<param name="wp-package" value="SocialSharing"/> |
||||
|
</feature> |
||||
|
``` |
||||
|
|
||||
|
For sharing remote images (or other files) on Android, the file needs to be stored locally first, so add this permission to `AndroidManifest.xml`: |
||||
|
```xml |
||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
||||
|
``` |
||||
|
|
||||
|
For iOS, you'll need to add the `Social.framework` and `MessageUI.framework` to your project. Click your project, Build Phases, Link Binary With Libraries, search for and add `Social.framework` and `MessageUI.framework`. |
||||
|
|
||||
|
2\. Grab a copy of SocialSharing.js, add it to your project and reference it in `index.html`: |
||||
|
```html |
||||
|
<script type="text/javascript" src="js/SocialSharing.js"></script> |
||||
|
``` |
||||
|
|
||||
|
3\. Download the source files for iOS and/or Android and copy them to your project. |
||||
|
|
||||
|
iOS: Copy `SocialSharing.h` and `SocialSharing.m` to `platforms/ios/<ProjectName>/Plugins` |
||||
|
|
||||
|
Android: Copy `SocialSharing.java` to `platforms/android/src/nl/xservices/plugins` (create the folders) |
||||
|
|
||||
|
Window Phone: Copy `SocialSharing.cs` to `platforms/wp8/Plugins/nl.x-services.plugins.socialsharing` (create the folders) |
||||
|
|
||||
|
### PhoneGap Build |
||||
|
Just add the following xml to your `config.xml` to always use the latest version of this plugin (which is published to plugins.cordova.io these days): |
||||
|
```xml |
||||
|
<gap:plugin name="cordova-plugin-x-socialsharing" source="npm" /> |
||||
|
``` |
||||
|
or to use an older version, hosted at phonegap build: |
||||
|
```xml |
||||
|
<gap:plugin name="nl.x-services.plugins.socialsharing" version="4.3.16" /> |
||||
|
``` |
||||
|
|
||||
|
SocialSharing.js is brought in automatically. Make sure though you include a reference to cordova.js in your index.html's head: |
||||
|
```html |
||||
|
<script type="text/javascript" src="cordova.js"></script> |
||||
|
``` |
||||
|
|
||||
|
## 4a. Usage on iOS and Android |
||||
|
You can share text, a subject (in case the user selects the email application), (any type and location of) file (like an image), and a link. |
||||
|
However, what exactly gets shared, depends on the application the user chooses to complete the action. A few examples: |
||||
|
- Mail: message, subject, file. |
||||
|
- Twitter: message, image (other filetypes are not supported), link (which is automatically shortened if the Twitter client deems it necessary). |
||||
|
- Google+ / Hangouts (Android only): message, subject, link |
||||
|
- Flickr: message, image (an image is required for this option to show up). |
||||
|
- Facebook Android: sharing a message is not possible. You can share either a link or an image (not both), but a description can not be prefilled. See [this Facebook issue which they won't solve](https://developers.facebook.com/x/bugs/332619626816423/). As an alternative you can use `shareViaFacebookWithPasteMessageHint` since plugin version 4.3.4. See below for details. Also note that sharing a URL on a non standard domain (like .fail) [may not work on Android](https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/issues/253). Make sure you test this. You can use a [link shortener](https://goo.gl) to workaround this issue. |
||||
|
- Facebook iOS: message, image (other filetypes are not supported), link. Beware that since a Fb update in April 2015 sharing a prefilled message is no longer possible when the Fb app is installed (like Android), see #344. Alternative: use `shareViaFacebookWithPasteMessageHint`. |
||||
|
|
||||
|
### Using the share sheet |
||||
|
Since version 5.1.0 (for iOS and Android) it's recommended to use `shareWithOptions` as it's the most feature rich way to share stuff cross-platform. |
||||
|
|
||||
|
It will also tell you if sharing to an app completed and which app that was (if that app plays nice, that is). |
||||
|
|
||||
|
```js |
||||
|
// this is the complete list of currently supported params you can pass to the plugin (all optional) |
||||
|
var options = { |
||||
|
message: 'share this', // not supported on some apps (Facebook, Instagram) |
||||
|
subject: 'the subject', // fi. for email |
||||
|
files: ['', ''], // an array of filenames either locally or remotely |
||||
|
url: 'https://www.website.com/foo/#bar?a=b', |
||||
|
chooserTitle: 'Pick an app' // Android only, you can override the default share sheet title |
||||
|
} |
||||
|
|
||||
|
var onSuccess = function(result) { |
||||
|
console.log("Share completed? " + result.completed); // On Android apps mostly return false even while it's true |
||||
|
console.log("Shared to app: " + result.app); // On Android result.app is currently empty. On iOS it's empty when sharing is cancelled (result.completed=false) |
||||
|
} |
||||
|
|
||||
|
var onError = function(msg) { |
||||
|
console.log("Sharing failed with message: " + msg); |
||||
|
} |
||||
|
|
||||
|
window.plugins.socialsharing.shareWithOptions(options, onSuccess, onError); |
||||
|
``` |
||||
|
|
||||
|
#### You can still use the older `share` method as well |
||||
|
Here are some examples you can copy-paste to test the various combinations: |
||||
|
```html |
||||
|
<button onclick="window.plugins.socialsharing.share('Message only')">message only</button> |
||||
|
<button onclick="window.plugins.socialsharing.share('Message and subject', 'The subject')">message and subject</button> |
||||
|
<button onclick="window.plugins.socialsharing.share(null, null, null, 'http://www.x-services.nl')">link only</button> |
||||
|
<button onclick="window.plugins.socialsharing.share('Message and link', null, null, 'http://www.x-services.nl')">message and link</button> |
||||
|
<button onclick="window.plugins.socialsharing.share(null, null, 'https://www.google.nl/images/srpr/logo4w.png', null)">image only</button> |
||||
|
// Beware: passing a base64 file as 'data:' is not supported on Android 2.x: https://code.google.com/p/android/issues/detail?id=7901#c43 |
||||
|
// Hint: when sharing a base64 encoded file on Android you can set the filename by passing it as the subject (second param) |
||||
|
<button onclick="window.plugins.socialsharing.share(null, 'Android filename', 'data:image/png;base64,R0lGODlhDAAMALMBAP8AAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAUKAAEALAAAAAAMAAwAQAQZMMhJK7iY4p3nlZ8XgmNlnibXdVqolmhcRQA7', null)">base64 image only</button> |
||||
|
// Hint: you can share multiple files by using an array as thirds param: ['file 1','file 2', ..], but beware of this Android Kitkat Facebook issue: [#164] |
||||
|
<button onclick="window.plugins.socialsharing.share('Message and image', null, 'https://www.google.nl/images/srpr/logo4w.png', null)">message and image</button> |
||||
|
<button onclick="window.plugins.socialsharing.share('Message, image and link', null, 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl')">message, image and link</button> |
||||
|
<button onclick="window.plugins.socialsharing.share('Message, subject, image and link', 'The subject', 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl')">message, subject, image and link</button> |
||||
|
``` |
||||
|
|
||||
|
Example: share a PDF file from the local www folder: |
||||
|
```html |
||||
|
<button onclick="window.plugins.socialsharing.share('Here is your PDF file', 'Your PDF', 'www/files/manual.pdf')">Share PDF</button> |
||||
|
``` |
||||
|
|
||||
|
### Sharing directly to.. |
||||
|
|
||||
|
####Twitter |
||||
|
```html |
||||
|
<!-- unlike most apps Twitter doesn't like it when you use an array to pass multiple files as the second param --> |
||||
|
<button onclick="window.plugins.socialsharing.shareViaTwitter('Message via Twitter')">message via Twitter</button> |
||||
|
<button onclick="window.plugins.socialsharing.shareViaTwitter('Message and link via Twitter', null /* img */, 'http://www.x-services.nl')">msg and link via Twitter</button> |
||||
|
``` |
||||
|
|
||||
|
####Facebook |
||||
|
```html |
||||
|
<button onclick="window.plugins.socialsharing.shareViaFacebook('Message via Facebook', null /* img */, null /* url */, function() {console.log('share ok')}, function(errormsg){alert(errormsg)})">msg via Facebook (with errcallback)</button> |
||||
|
``` |
||||
|
|
||||
|
Facebook with prefilled message - as a workaround for [this Facebook (Android) bug](https://developers.facebook.com/x/bugs/332619626816423/). BEWARE: it's against Facebooks policy to prefil the message, or even hint the user to prefill a message for them. [See this page for details.](https://developers.facebook.com/docs/apps/review/prefill) |
||||
|
|
||||
|
* On Android the user will see a Toast message with a message you control (default: "If you like you can paste a message from your clipboard"). |
||||
|
* On iOS this function used to behave the same as `shareViaFacebook`, but since 4.3.18 a short message is shown prompting the user to paste a message (like Android). This message is not shown in case the Fb app is not installed since the internal iOS Fb share widget still supports prefilling the message. |
||||
|
* iOS9 quirk: if you want to use this method, you need to whitelist `fb://` in your .plist file. |
||||
|
```html |
||||
|
<button onclick="window.plugins.socialsharing.shareViaFacebookWithPasteMessageHint('Message via Facebook', null /* img */, null /* url */, 'Paste it dude!', function() {console.log('share ok')}, function(errormsg){alert(errormsg)})">msg via Facebook (with errcallback)</button> |
||||
|
``` |
||||
|
|
||||
|
Whitelisting Facebook in your app's .plist: |
||||
|
```xml |
||||
|
<key>LSApplicationQueriesSchemes</key> |
||||
|
<array> |
||||
|
<string>fb</string> |
||||
|
</array> |
||||
|
``` |
||||
|
|
||||
|
####Instagram |
||||
|
```html |
||||
|
<button onclick="window.plugins.socialsharing.shareViaInstagram('Message via Instagram', 'https://www.google.nl/images/srpr/logo4w.png', function() {console.log('share ok')}, function(errormsg){alert(errormsg)})">msg via Instagram</button> |
||||
|
``` |
||||
|
|
||||
|
Quirks: |
||||
|
* Instagram no longer permits prefilling a message - you can prompt the user to paste the message you've passed to the plugin because we're adding it to the clipboard for you. |
||||
|
|
||||
|
iOS Quirks: |
||||
|
* Before trying to invoke `shareViaInstagram` please use `canShareVia('instagram'..` AND whitelist the urlscheme (see below). |
||||
|
* Although this plugin follows the Instagram sharing guidelines, the user may not only see Instagram in the share sheet, but also other apps that listen to the "Instagram sharing ID". Just google "com.instagram.exclusivegram" and you see what I mean. |
||||
|
|
||||
|
|
||||
|
#### WhatsApp |
||||
|
* Note that on iOS when sharing an image and text, only the image is shared - let's hope WhatsApp creates a proper iOS extension to fix this. |
||||
|
* Before using this method you may want to use `canShareVia('whatsapp'..` (see below). |
||||
|
```html |
||||
|
<button onclick="window.plugins.socialsharing.shareViaWhatsApp('Message via WhatsApp', null /* img */, null /* url */, function() {console.log('share ok')}, function(errormsg){alert(errormsg)})">msg via WhatsApp (with errcallback)</button> |
||||
|
``` |
||||
|
|
||||
|
##### Experimental feature: sharing directly to someone |
||||
|
Available in 5.0.8 and up - please let me know if this works for your device! Open an issue if not.. |
||||
|
|
||||
|
```html |
||||
|
<button onclick="window.plugins.socialsharing.shareViaWhatsAppToReceiver(receiver, 'Message via WhatsApp', null /* img */, null /* url */, function() {console.log('share ok')})">msg via WhatsApp for Addressbook ID 101</button> |
||||
|
``` |
||||
|
For `receiver` on iOS pass in the Addressbook ID (or 'abid'). You can find those abid's by using the [Cordova Contacts Plugin](https://github.com/apache/cordova-plugin-contacts). |
||||
|
The result in the success callback of the `find` function is a JSON array of contact objects, use the 'id' you find in those objects. |
||||
|
Don't pass in an image on iOS because that can't be sent to someone directly unfortunately. Message and URL are fine though. |
||||
|
|
||||
|
On Android pass in the phone number of the person you want to send a message to (untested at the moment). |
||||
|
|
||||
|
####SMS |
||||
|
Note that on Android, SMS via Hangouts may not behave correctly |
||||
|
```html |
||||
|
<!-- Want to share a prefilled SMS text? --> |
||||
|
<button onclick="window.plugins.socialsharing.shareViaSMS('My cool message', null /* see the note below */, function(msg) {console.log('ok: ' + msg)}, function(msg) {alert('error: ' + msg)})">share via SMS</button> |
||||
|
<!-- Want to prefill some phonenumbers as well? Pass this instead of null. Important notes: For stable usage of shareViaSMS on Android 4.4 and up you require to add at least one phonenumber! Also, on Android make sure you use v4.0.3 or higher of this plugin, otherwise sharing multiple numbers to non-Samsung devices will fail --> |
||||
|
<button onclick="window.plugins.socialsharing.shareViaSMS('My cool message', '0612345678,0687654321', function(msg) {console.log('ok: ' + msg)}, function(msg) {alert('error: ' + msg)})">share via SMS</button> |
||||
|
<!-- Need a subject and even image sharing? It's only supported on iOS for now and falls back to just message sharing on Android --> |
||||
|
<button onclick="window.plugins.socialsharing.shareViaSMS({'message':'My cool message', 'subject':'The subject', 'image':'https://www.google.nl/images/srpr/logo4w.png'}, '0612345678,0687654321', function(msg) {console.log('ok: ' + msg)}, function(msg) {alert('error: ' + msg)})">share via SMS</button> |
||||
|
``` |
||||
|
|
||||
|
####Email |
||||
|
Code inspired by the [EmailComposer plugin](https://github.com/katzer/cordova-plugin-email-composer), note that this is not supported on the iOS 8 simulator (an alert will be shown if your try to). |
||||
|
```js |
||||
|
window.plugins.socialsharing.shareViaEmail( |
||||
|
'Message', // can contain HTML tags, but support on Android is rather limited: http://stackoverflow.com/questions/15136480/how-to-send-html-content-with-image-through-android-default-email-client |
||||
|
'Subject', |
||||
|
['to@person1.com', 'to@person2.com'], // TO: must be null or an array |
||||
|
['cc@person1.com'], // CC: must be null or an array |
||||
|
null, // BCC: must be null or an array |
||||
|
['https://www.google.nl/images/srpr/logo4w.png','www/localimage.png'], // FILES: can be null, a string, or an array |
||||
|
onSuccess, // called when sharing worked, but also when the user cancelled sharing via email. On iOS, the callbacks' boolean result parameter is true when sharing worked, false if cancelled. On Android, this parameter is always true so it can't be used). See section "Notes about the successCallback" below. |
||||
|
onError // called when sh*t hits the fan |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
If Facebook, Twitter, Instagram, WhatsApp, SMS or Email is not available, the errorCallback is called with the text 'not available'. |
||||
|
|
||||
|
If you feel lucky, you can even try to start any application with the `shareVia` function: |
||||
|
```html |
||||
|
<!-- start facebook on iOS (same as `shareViaFacebook`), if Facebook is not installed, the errorcallback will be invoked with message 'not available' --> |
||||
|
<button onclick="window.plugins.socialsharing.shareVia('com.apple.social.facebook', 'Message via FB', null, null, null, function(){console.log('share ok')}, function(msg) {alert('error: ' + msg)})">message via Facebook</button> |
||||
|
<!-- start facebook on Android (same as `shareViaFacebook`), if Facebook is not installed, the errorcallback will be invoked with message 'not available' --> |
||||
|
<button onclick="window.plugins.socialsharing.shareVia('facebook', 'Message via FB', null, null, null, function(){console.log('share ok')}, function(msg) {alert('error: ' + msg)})">message via Facebook</button> |
||||
|
<!-- start twitter on iOS (same as `shareViaTwitter`), if Twitter is not installed, the errorcallback will be invoked with message 'not available' --> |
||||
|
<button onclick="window.plugins.socialsharing.shareVia('com.apple.social.twitter', 'Message via Twitter', null, null, 'http://www.x-services.nl', function(){console.log('share ok')}, function(msg) {alert('error: ' + msg)})">message and link via Twitter on iOS</button> |
||||
|
<!-- if you share to a non existing/supported app, the errorcallback will be invoked with message 'not available' --> |
||||
|
<button onclick="window.plugins.socialsharing.shareVia('bogus_app', 'Message via Bogus App', null, null, null, function(){console.log('share ok')}, function(msg) {alert('error: ' + msg)})">message via Bogus App</button> |
||||
|
``` |
||||
|
|
||||
|
What can we pass to the `shareVia` function? |
||||
|
* iOS: You are limited to 'com.apple.social.[facebook | twitter | sinaweibo | tencentweibo]'. If an app does not exist, the errorcallback is invoked and iOS shows a popup message asking the user to configure the app. |
||||
|
* Android: Anything that would otherwise appear in the sharing dialoge (in case the `share` function was used. Pass a (part of the) packagename of the app you want to share to. The `shareViaFacebook` function for instance uses `com.facebook.katana` as the packagename fragment. Things like `weibo`, `pinterest` and `com.google.android.apps.plus` (Google+) should work just fine. |
||||
|
|
||||
|
You can even test if a sharing option is available with `canShareVia`! |
||||
|
You'll need to pass everything you want to share, because (at least on Android) some apps may only become available when an image is added. |
||||
|
The function will invoke the successCallback when it can be shared to via `shareVia`, and the errorCallback if not. As a bonus on Android, the errorCallback contains a JSON Array of available packages you can pass to shareVia. |
||||
|
You can even specify the activity if the app offers multiple sharing ways, passing 'packageName/activityName'. (for example, WeChat, passing 'com.tencent.mm' or 'com.tencent.mm/com.tencent.mm.ui.tools.ShareImgUI' to share to chat, passing 'com.tencent.mm/com.tencent.mm.ui.tools.ShareToTimeLineUI' to share to moments). |
||||
|
|
||||
|
```html |
||||
|
<button onclick="window.plugins.socialsharing.canShareVia('com.tencent.mm/com.tencent.mm.ui.tools.ShareToTimeLineUI', 'msg', null, img, null, function(e){alert(e)}, function(e){alert(e)})">is WeChat available on Android?</button> |
||||
|
<button onclick="window.plugins.socialsharing.canShareVia('com.apple.social.facebook', 'msg', null, null, null, function(e){alert(e)}, function(e){alert(e)})">is facebook available on iOS?</button> |
||||
|
// this one requires whitelisting of whatsapp:// on iOS9 in your .plist file |
||||
|
<button onclick="window.plugins.socialsharing.canShareVia('whatsapp', 'msg', null, null, null, function(e){alert(e)}, function(e){alert(e)})">is WhatsApp available?</button> |
||||
|
<button onclick="window.plugins.socialsharing.canShareVia('sms', 'msg', null, null, null, function(e){alert(e)}, function(e){alert(e)})">is SMS available?</button> |
||||
|
<button onclick="window.plugins.socialsharing.canShareVia('instagram', 'msg', null, null, null, function(e){alert(e)}, function(e){alert(e)})">is Instagram available?</button> |
||||
|
<!-- Email is a different beast, so I added a specific method for it --> |
||||
|
<button onclick="window.plugins.socialsharing.canShareViaEmail(function(e){alert(e)}, function(e){alert(e)})">is Email available?</button> |
||||
|
``` |
||||
|
|
||||
|
Want to share images from a local folder (like an image you just selected from the CameraRoll)? |
||||
|
```javascript |
||||
|
// use a local image from inside the www folder: |
||||
|
window.plugins.socialsharing.share(null, null, 'www/image.gif', null); // success/error callback params may be added as 5th and 6th param |
||||
|
// .. or a local image from anywhere else (if permitted): |
||||
|
// local-iOS: |
||||
|
window.plugins.socialsharing.share(null, null, '/Users/username/Library/Application Support/iPhone/6.1/Applications/25A1E7CF-079F-438D-823B-55C6F8CD2DC0/Documents/.nl.x-services.appname/pics/img.jpg'); |
||||
|
// local-iOS-alt: |
||||
|
window.plugins.socialsharing.share(null, null, 'file:///Users/username/Library/Application Support/iPhone/6.1/Applications/25A1E7CF-079F-438D-823B-55C6F8CD2DC0/Documents/.nl.x-services.appname/pics/img.jpg'); |
||||
|
// local-Android: |
||||
|
window.plugins.socialsharing.share(null, null, 'file:///storage/emulated/0/nl.xservices.testapp/5359/Photos/16832/Thumb.jpg'); |
||||
|
// .. or an image from the internet: |
||||
|
window.plugins.socialsharing.share(null, null, 'http://domain.com/image.jpg'); |
||||
|
``` |
||||
|
|
||||
|
If your app still supports iOS5, you'll want to check whether or not the plugin is available as it only supports iOS6 and up. |
||||
|
```javascript |
||||
|
window.plugins.socialsharing.available(function(isAvailable) { |
||||
|
// the boolean is only false on iOS < 6 |
||||
|
if (isAvailable) { |
||||
|
// now use any of the share() functions |
||||
|
} |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
If you can't get the plugin to work, have a look at [this demo project](https://github.com/EddyVerbruggen/X-Services-PhoneGap-Build-Plugins-Demo). |
||||
|
|
||||
|
#### Notes about the successCallback (you can just ignore the callbacks if you like) |
||||
|
Since version 3.8 the plugin passes a boolean to the successCallback to let the app know whether or not content was actually shared, or the share widget was closed by the user. |
||||
|
On iOS this works as expected (except for Facebook, in case the app is installed), but on Android some sharing targets may return false, even though sharing succeeded. This is not a limitation of the plugin, it's the target app which doesn't play nice. |
||||
|
To make it more confusing, when sharing via SMS on Android, you'll likely always have the successCallback invoked. Thanks Google. |
||||
|
|
||||
|
#### Sharing multiple images (or other files) |
||||
|
Since version 4.3.0 of this plugin you can pass an array of files to the share and shareVia functions. |
||||
|
```js |
||||
|
// sharing multiple images via Facebook (you can mix protocols and file locations) |
||||
|
window.plugins.socialsharing.shareViaFacebook( |
||||
|
'Optional message, may be ignored by Facebook app', |
||||
|
['https://www.google.nl/images/srpr/logo4w.png','www/image.gif'], |
||||
|
null); |
||||
|
|
||||
|
// sharing a PDF and an image |
||||
|
window.plugins.socialsharing.share( |
||||
|
'Optional message', |
||||
|
'Optional title', |
||||
|
['www/manual.pdf','https://www.google.nl/images/srpr/logo4w.png'], |
||||
|
'http://www.myurl.com'); |
||||
|
``` |
||||
|
|
||||
|
Note that a lot of apps support sharing multiple files, but Twitter just doesn't accept more that one file. |
||||
|
|
||||
|
#### Saving images to the photo album (iOS only currently) |
||||
|
Since version 4.3.16 of this plugin you can save an array of images to the camera roll: |
||||
|
```js |
||||
|
window.plugins.socialsharing.saveToPhotoAlbum( |
||||
|
['https://www.google.nl/images/srpr/logo4w.png','www/image.gif'], |
||||
|
onSuccess, // optional success function |
||||
|
onError // optional error function |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
#### iOS quirk (with camera plugin) |
||||
|
When using this plugin in the callback of the Phonegap camera plugin, wrap the call to `share()` in a `setTimeout()`. |
||||
|
The share widget has the same limitation as the alert dialogue [mentioned in the Phonegap documentation](http://docs.phonegap.com/en/2.9.0/cordova_camera_camera.md.html#camera.getPicture_ios_quirks). |
||||
|
|
||||
|
#### Excluding some options from the widget |
||||
|
If you want to exclude (for example) the assign-to-contact and copy-to-pasteboard options, add this to your main plist file: |
||||
|
|
||||
|
```xml |
||||
|
<key>SocialSharingExcludeActivities</key> |
||||
|
<array> |
||||
|
<string>com.apple.UIKit.activity.AssignToContact</string> |
||||
|
<string>com.apple.UIKit.activity.CopyToPasteboard</string> |
||||
|
</array> |
||||
|
``` |
||||
|
|
||||
|
Here's the list of available activities you can disable : |
||||
|
|
||||
|
- com.apple.UIKit.activity.PostToFacebook |
||||
|
- com.apple.UIKit.activity.PostToTwitter |
||||
|
- com.apple.UIKit.activity.PostToFlickr |
||||
|
- com.apple.UIKit.activity.PostToWeibo |
||||
|
- com.apple.UIKit.activity.PostToVimeo |
||||
|
- com.apple.UIKit.activity.TencentWeibo |
||||
|
- com.apple.UIKit.activity.Message |
||||
|
- com.apple.UIKit.activity.Mail |
||||
|
- com.apple.UIKit.activity.Print |
||||
|
- com.apple.UIKit.activity.CopyToPasteboard |
||||
|
- com.apple.UIKit.activity.AssignToContact |
||||
|
- com.apple.UIKit.activity.SaveToCameraRoll |
||||
|
- com.apple.UIKit.activity.AddToReadingList |
||||
|
- com.apple.UIKit.activity.AirDrop |
||||
|
|
||||
|
|
||||
|
## 4b. Usage on Windows Phone |
||||
|
The available methods on WP8 are: `available`, `canShareViaEmail`, `share`, `shareViaEmail` and `shareViaSMS`. |
||||
|
Currently the first two always return true, but this may change in the future in case I can find a way to truly detect the availability. |
||||
|
|
||||
|
The `share` function on WP8 supports two flavours: message only, or a combination of message, title and link. |
||||
|
|
||||
|
Beware: for now please pass null values for all non used attributes, like in the examples below. |
||||
|
|
||||
|
Sharing a message: |
||||
|
```html |
||||
|
<button onclick="window.plugins.socialsharing.share('Message only', null, null, null)">message only</button> |
||||
|
``` |
||||
|
|
||||
|
Sharing a link: |
||||
|
```html |
||||
|
<button onclick="window.plugins.socialsharing.share('Optional message', 'Optional title', null, 'http://www.x-services.nl')">message, title, link</button> |
||||
|
``` |
||||
|
|
||||
|
Sharing an image (only images from the internet are supported). If you pass more than one image as an array, only the first one is used: |
||||
|
```html |
||||
|
<button onclick="window.plugins.socialsharing.share('Optional message', 'Optional title', 'https://www.google.nl/images/srpr/logo4w.png', null)">image only</button> |
||||
|
``` |
||||
|
|
||||
|
## 4c. Share-popover on iPad |
||||
|
Carlos Sola-Llonch, a user of this plugin, pointed me at an [iOS document](https://developer.apple.com/library/ios/documentation/uikit/reference/UIActivityViewController_Class/Reference/Reference.html) |
||||
|
stating "On iPad, you must present the view controller in a popover. On iPhone and iPod touch, you must present it modally." |
||||
|
|
||||
|
He also provided me with the required code to do so (thanks!). I've adapted it a little to make sure current behaviour is |
||||
|
not altered, but with a little extra effort you can use this new popover feature. |
||||
|
|
||||
|
The trick is overriding the function `window.plugins.socialsharing.iPadPopupCoordinates` by your own implementation |
||||
|
to tell the iPad where to show the popup exactly. It need to be a string like "100,200,300,300" (left,top,width,height). |
||||
|
|
||||
|
You need to override this method after `deviceready` has fired. |
||||
|
|
||||
|
You have various options, like checking the click event on a button and determine the event.clientX and event.clientY, |
||||
|
or use this code Carlos showed me to grab the coordinates of a static button somewhere on your page: |
||||
|
|
||||
|
```js |
||||
|
window.plugins.socialsharing.iPadPopupCoordinates = function() { |
||||
|
var rect = document.getElementById('share_button').getBoundingClientRect(); |
||||
|
return rect.left + "," + rect.top + "," + rect.width + "," + rect.height; |
||||
|
}; |
||||
|
``` |
||||
|
|
||||
|
Note that since iOS 8 this popup is the only way Apple allows you to share stuff, so this plugin has been adjusted to use this plugin as standard for iOS 8 and positions |
||||
|
the popup at the bottom of the screen (seems like a logical default because that's where it previously was as well). |
||||
|
You can however override this position in the same way as explained above. |
||||
|
|
||||
|
**Note**: when using the [WkWebView polyfill](https://github.com/Telerik-Verified-Plugins/WKWebView) the `iPadPopupCoordinates` overrides [doesn't work](https://github.com/Telerik-Verified-Plugins/WKWebView/issues/77) so you can call the alternative `setIPadPopupCoordinates` method to define the popup position just before you call the `share` method. |
||||
|
|
||||
|
example : |
||||
|
|
||||
|
```js |
||||
|
var targetRect = event.targetElement.getBoundingClientRect(), |
||||
|
targetBounds = targetRect.left + ',' + targetRect.top + ',' + targetRect.width + ',' + targetRect.height; |
||||
|
|
||||
|
window.plugins.socialsharing.setIPadPopupCoordinates(targetBounds); |
||||
|
window.plugins.socialsharing.share('Hello from iOS :)') |
||||
|
``` |
||||
|
|
||||
|
## 4d. Whitelisting on iOS 9 |
||||
|
|
||||
|
On iOS 9 you have to make sure to whitelist the applications you want to use for sharing. Without whitelisting "query schemes", you may get the error callback invoked when calling the `canShareVia` function (and possibly the `shareVia`). You can verify this is a permissions issue by observing the output in XCode for something like: |
||||
|
|
||||
|
> -canOpenURL: failed for URL: "whatsapp://app" - error: "This app is not allowed to query for scheme whatsapp" |
||||
|
|
||||
|
You have a few options to prevent this by whitelisting the application you want to share via: |
||||
|
|
||||
|
### Directly editing the .plist file |
||||
|
This is a stright forward approach - you just manually edit the .plist file - either from within XCode or using a text editor. You can see example entries above (e.g. xyz). While this is simple to do, the changes may be lost when rebuilding the project or tweaking the platform (e.g. upgrading) and is less recomended. |
||||
|
|
||||
|
### Use query schema plugin |
||||
|
There is a plugin designed specifically to address query schema whitelisting. You can find the plugin and how to use it [here](https://www.npmjs.com/package/cordova-plugin-queries-schemes). In general, after installation, you can change plugin.xml file under the plugin subfolder within the plugins directory of your project to add the required schemas. Here again though, you have to edit an additional file and should take care not to overwrite it when making changes to your project. |
||||
|
|
||||
|
### Use Custom Config plugin |
||||
|
The Custom Config plugin ([here](https://github.com/dpa99c/cordova-custom-config)) allows you to add configuration to your platforms "native" configuration files (e.g. .plist or AndroidManifest.xml) through the project's main config.xml file. |
||||
|
|
||||
|
To address query schema issue, after installaing the plugin you can edit the iOS platform section of your config.xml (in the project main folder) to include the required entries: |
||||
|
|
||||
|
```xml |
||||
|
<?xml version='1.0' encoding='utf-8'?> |
||||
|
<widget id="your.app.id" |
||||
|
version="0.9.1" |
||||
|
xmlns="http://www.w3.org/ns/widgets" |
||||
|
xmlns:cdv="http://cordova.apache.org/ns/1.0"> |
||||
|
|
||||
|
<!-- a bunch of elements like name, description etc --> |
||||
|
|
||||
|
<platform name="ios"> |
||||
|
|
||||
|
<!-- add this entry --> |
||||
|
<config-file platform="ios" target="*-Info.plist" parent="LSApplicationQueriesSchemes"> |
||||
|
<array> |
||||
|
<string>whatsapp</string> |
||||
|
<!-- add more query scheme strings --> |
||||
|
</array> |
||||
|
</config-file> |
||||
|
</platform> |
||||
|
</widget> |
||||
|
``` |
||||
|
|
||||
|
The advantage with this method is that editing is done in the config.xml file which will most often be in your source control anyway and hence, changes to it will be reserved. |
||||
|
|
||||
|
## 5. Credits ## |
||||
|
|
||||
|
This plugin was enhanced for Plugman / PhoneGap Build by [Eddy Verbruggen](http://www.x-services.nl). |
||||
|
The Android and Windows Phone code was entirely created by the author. |
||||
|
The first iteration of the iOS code was inspired by [Cameron Lerch](https://github.com/bfcam/phonegap-ios-social-plugin). |
@ -0,0 +1,46 @@ |
|||||
|
{ |
||||
|
"name": "cordova-plugin-x-socialsharing", |
||||
|
"version": "5.1.6", |
||||
|
"description": "Share text, images (and other files), or a link via the native sharing widget of your device. Android is fully supported, as well as iOS 6 and up. WP8 has somewhat limited support.", |
||||
|
"cordova": { |
||||
|
"id": "cordova-plugin-x-socialsharing", |
||||
|
"platforms": [ |
||||
|
"ios", |
||||
|
"android", |
||||
|
"wp8" |
||||
|
] |
||||
|
}, |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "git+https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin.git" |
||||
|
}, |
||||
|
"keywords": [ |
||||
|
"Social", |
||||
|
"Share", |
||||
|
"Twitter", |
||||
|
"Facebook", |
||||
|
"Email", |
||||
|
"SMS", |
||||
|
"WhatsApp", |
||||
|
"Tumblr", |
||||
|
"Pocket", |
||||
|
"LinkedIn", |
||||
|
"cordova", |
||||
|
"ecosystem:cordova", |
||||
|
"cordova-ios", |
||||
|
"cordova-android", |
||||
|
"cordova-windows" |
||||
|
], |
||||
|
"engines": [ |
||||
|
{ |
||||
|
"name": "cordova", |
||||
|
"version": ">=3.0.0" |
||||
|
} |
||||
|
], |
||||
|
"author": "Eddy Verbruggen - @EddyVerbruggen", |
||||
|
"license": "MIT", |
||||
|
"bugs": { |
||||
|
"url": "https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/issues" |
||||
|
}, |
||||
|
"homepage": "https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin#readme" |
||||
|
} |
@ -0,0 +1,96 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" |
||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
id="cordova-plugin-x-socialsharing" |
||||
|
version="5.1.6"> |
||||
|
|
||||
|
<name>SocialSharing</name> |
||||
|
|
||||
|
<description> |
||||
|
Share text, images (and other files), or a link via the native sharing widget of your device. |
||||
|
Android is fully supported, as well as iOS 6 and up. WP8 has somewhat limited support. |
||||
|
</description> |
||||
|
|
||||
|
<author>Eddy Verbruggen</author> |
||||
|
|
||||
|
<license>MIT</license> |
||||
|
|
||||
|
<keywords>Social, Share, Twitter, Facebook, Email, SMS, WhatsApp, Tumblr, Pocket, LinkedIn</keywords> |
||||
|
|
||||
|
<repo>https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin.git</repo> |
||||
|
|
||||
|
<issue>https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/issues</issue> |
||||
|
|
||||
|
<engines> |
||||
|
<engine name="cordova" version=">=3.0.0"/> |
||||
|
</engines> |
||||
|
|
||||
|
<js-module src="www/SocialSharing.js" name="SocialSharing"> |
||||
|
<clobbers target="window.plugins.socialsharing" /> |
||||
|
</js-module> |
||||
|
|
||||
|
<!-- ios --> |
||||
|
<platform name="ios"> |
||||
|
|
||||
|
<config-file target="config.xml" parent="/*"> |
||||
|
<feature name="SocialSharing"> |
||||
|
<param name="ios-package" value="SocialSharing"/> |
||||
|
<param name="onload" value="true" /> |
||||
|
</feature> |
||||
|
</config-file> |
||||
|
|
||||
|
<header-file src="src/ios/NSString+URLEncoding.h"/> |
||||
|
<source-file src="src/ios/NSString+URLEncoding.m"/> |
||||
|
<header-file src="src/ios/SocialSharing.h"/> |
||||
|
<source-file src="src/ios/SocialSharing.m"/> |
||||
|
|
||||
|
<framework src="Social.framework" weak="true" /> |
||||
|
<framework src="MessageUI.framework" weak="true" /> |
||||
|
</platform> |
||||
|
|
||||
|
<!-- android --> |
||||
|
<platform name="android"> |
||||
|
|
||||
|
<config-file target="res/xml/config.xml" parent="/*"> |
||||
|
<feature name="SocialSharing"> |
||||
|
<param name="android-package" value="nl.xservices.plugins.SocialSharing" /> |
||||
|
</feature> |
||||
|
</config-file> |
||||
|
|
||||
|
<config-file target="AndroidManifest.xml" parent="/*"> |
||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> |
||||
|
</config-file> |
||||
|
|
||||
|
<config-file target="AndroidManifest.xml" parent="/manifest/application"> |
||||
|
<provider android:authorities="$PACKAGE_NAME.sharing.provider" android:exported="false" android:grantUriPermissions="true" android:name="nl.xservices.plugins.FileProvider"> |
||||
|
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/sharing_paths" /> |
||||
|
</provider> |
||||
|
</config-file> |
||||
|
|
||||
|
<source-file src="src/android/nl/xservices/plugins/SocialSharing.java" target-dir="src/nl/xservices/plugins"/> |
||||
|
<source-file src="src/android/nl/xservices/plugins/FileProvider.java" target-dir="src/nl/xservices/plugins"/> |
||||
|
<source-file src="src/android/res/xml/sharing_paths.xml" target-dir="res/xml"/> |
||||
|
|
||||
|
<framework src="com.android.support:support-v4:24.1.1+" /> |
||||
|
</platform> |
||||
|
|
||||
|
<!-- wp8 --> |
||||
|
<platform name="wp8"> |
||||
|
<config-file target="config.xml" parent="/*"> |
||||
|
<feature name="SocialSharing"> |
||||
|
<param name="wp-package" value="SocialSharing"/> |
||||
|
</feature> |
||||
|
</config-file> |
||||
|
|
||||
|
<source-file src="src/wp8/SocialSharing.cs" /> |
||||
|
<framework src="src/wp8/Newtonsoft.Json.dll" custom="true"/> |
||||
|
</platform> |
||||
|
|
||||
|
<!-- windows --> |
||||
|
<platform name="windows"> |
||||
|
<js-module src="src/windows/SocialSharingProxy.js" name="SocialSharingProxy"> |
||||
|
<runs /> |
||||
|
</js-module> |
||||
|
</platform> |
||||
|
|
||||
|
</plugin> |
@ -0,0 +1,5 @@ |
|||||
|
package nl.xservices.plugins; |
||||
|
|
||||
|
|
||||
|
public class FileProvider extends android.support.v4.content.FileProvider { |
||||
|
} |
@ -0,0 +1,795 @@ |
|||||
|
package nl.xservices.plugins; |
||||
|
|
||||
|
import android.annotation.SuppressLint; |
||||
|
import android.app.Activity; |
||||
|
import android.content.*; |
||||
|
import android.content.pm.ActivityInfo; |
||||
|
import android.content.pm.PackageManager; |
||||
|
import android.content.pm.ResolveInfo; |
||||
|
import android.net.Uri; |
||||
|
import android.os.Build; |
||||
|
import android.os.Environment; |
||||
|
import android.text.Html; |
||||
|
import android.util.Base64; |
||||
|
import android.view.Gravity; |
||||
|
import android.widget.Toast; |
||||
|
|
||||
|
import org.apache.cordova.CallbackContext; |
||||
|
import org.apache.cordova.CordovaInterface; |
||||
|
import org.apache.cordova.CordovaPlugin; |
||||
|
import org.apache.cordova.PluginResult; |
||||
|
import org.json.JSONArray; |
||||
|
import org.json.JSONException; |
||||
|
import org.json.JSONObject; |
||||
|
|
||||
|
import java.io.*; |
||||
|
import java.net.URL; |
||||
|
import java.net.URLConnection; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
import java.util.Timer; |
||||
|
import java.util.TimerTask; |
||||
|
import java.util.regex.Matcher; |
||||
|
import java.util.regex.Pattern; |
||||
|
|
||||
|
import android.support.v4.content.FileProvider; |
||||
|
|
||||
|
public class SocialSharing extends CordovaPlugin { |
||||
|
|
||||
|
private static final String ACTION_AVAILABLE_EVENT = "available"; |
||||
|
private static final String ACTION_SHARE_EVENT = "share"; |
||||
|
private static final String ACTION_SHARE_WITH_OPTIONS_EVENT = "shareWithOptions"; |
||||
|
private static final String ACTION_CAN_SHARE_VIA = "canShareVia"; |
||||
|
private static final String ACTION_CAN_SHARE_VIA_EMAIL = "canShareViaEmail"; |
||||
|
private static final String ACTION_SHARE_VIA = "shareVia"; |
||||
|
private static final String ACTION_SHARE_VIA_TWITTER_EVENT = "shareViaTwitter"; |
||||
|
private static final String ACTION_SHARE_VIA_FACEBOOK_EVENT = "shareViaFacebook"; |
||||
|
private static final String ACTION_SHARE_VIA_FACEBOOK_WITH_PASTEMESSAGEHINT = "shareViaFacebookWithPasteMessageHint"; |
||||
|
private static final String ACTION_SHARE_VIA_WHATSAPP_EVENT = "shareViaWhatsApp"; |
||||
|
private static final String ACTION_SHARE_VIA_INSTAGRAM_EVENT = "shareViaInstagram"; |
||||
|
private static final String ACTION_SHARE_VIA_SMS_EVENT = "shareViaSMS"; |
||||
|
private static final String ACTION_SHARE_VIA_EMAIL_EVENT = "shareViaEmail"; |
||||
|
|
||||
|
private static final int ACTIVITY_CODE_SEND__BOOLRESULT = 1; |
||||
|
private static final int ACTIVITY_CODE_SEND__OBJECT = 2; |
||||
|
private static final int ACTIVITY_CODE_SENDVIAEMAIL = 3; |
||||
|
private static final int ACTIVITY_CODE_SENDVIAWHATSAPP = 4; |
||||
|
|
||||
|
private CallbackContext _callbackContext; |
||||
|
|
||||
|
private String pasteMessage; |
||||
|
|
||||
|
private abstract class SocialSharingRunnable implements Runnable { |
||||
|
public CallbackContext callbackContext; |
||||
|
|
||||
|
SocialSharingRunnable(CallbackContext cb) { |
||||
|
this.callbackContext = cb; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { |
||||
|
this._callbackContext = callbackContext; // only used for onActivityResult |
||||
|
this.pasteMessage = null; |
||||
|
if (ACTION_AVAILABLE_EVENT.equals(action)) { |
||||
|
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); |
||||
|
return true; |
||||
|
} else if (ACTION_SHARE_EVENT.equals(action)) { |
||||
|
return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), null, null, false, true); |
||||
|
} else if (ACTION_SHARE_WITH_OPTIONS_EVENT.equals(action)) { |
||||
|
return shareWithOptions(callbackContext, args.getJSONObject(0)); |
||||
|
} else if (ACTION_SHARE_VIA_TWITTER_EVENT.equals(action)) { |
||||
|
return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "twitter", null, false, true); |
||||
|
} else if (ACTION_SHARE_VIA_FACEBOOK_EVENT.equals(action)) { |
||||
|
return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "com.facebook.katana", null, false, true, "com.facebook.composer.shareintent"); |
||||
|
} else if (ACTION_SHARE_VIA_FACEBOOK_WITH_PASTEMESSAGEHINT.equals(action)) { |
||||
|
this.pasteMessage = args.getString(4); |
||||
|
return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "com.facebook.katana", null, false, true, "com.facebook.composer.shareintent"); |
||||
|
} else if (ACTION_SHARE_VIA_WHATSAPP_EVENT.equals(action)) { |
||||
|
if (notEmpty(args.getString(4))) { |
||||
|
return shareViaWhatsAppDirectly(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4)); |
||||
|
} else { |
||||
|
return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "whatsapp", null, false, true); |
||||
|
} |
||||
|
} else if (ACTION_SHARE_VIA_INSTAGRAM_EVENT.equals(action)) { |
||||
|
if (notEmpty(args.getString(0))) { |
||||
|
copyHintToClipboard(args.getString(0), "Instagram paste message"); |
||||
|
} |
||||
|
return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "instagram", null, false, true); |
||||
|
} else if (ACTION_CAN_SHARE_VIA.equals(action)) { |
||||
|
return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4), null, true, true); |
||||
|
} else if (ACTION_CAN_SHARE_VIA_EMAIL.equals(action)) { |
||||
|
if (isEmailAvailable()) { |
||||
|
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); |
||||
|
return true; |
||||
|
} else { |
||||
|
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, "not available")); |
||||
|
return false; |
||||
|
} |
||||
|
} else if (ACTION_SHARE_VIA.equals(action)) { |
||||
|
return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4), null, false, true); |
||||
|
} else if (ACTION_SHARE_VIA_SMS_EVENT.equals(action)) { |
||||
|
return invokeSMSIntent(callbackContext, args.getJSONObject(0), args.getString(1)); |
||||
|
} else if (ACTION_SHARE_VIA_EMAIL_EVENT.equals(action)) { |
||||
|
return invokeEmailIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.isNull(3) ? null : args.getJSONArray(3), args.isNull(4) ? null : args.getJSONArray(4), args.isNull(5) ? null : args.getJSONArray(5)); |
||||
|
} else { |
||||
|
callbackContext.error("socialSharing." + action + " is not a supported function. Did you mean '" + ACTION_SHARE_EVENT + "'?"); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean isEmailAvailable() { |
||||
|
final Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "someone@domain.com", null)); |
||||
|
return cordova.getActivity().getPackageManager().queryIntentActivities(intent, 0).size() > 0; |
||||
|
} |
||||
|
|
||||
|
private boolean invokeEmailIntent(final CallbackContext callbackContext, final String message, final String subject, final JSONArray to, final JSONArray cc, final JSONArray bcc, final JSONArray files) throws JSONException { |
||||
|
|
||||
|
final SocialSharing plugin = this; |
||||
|
cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) { |
||||
|
public void run() { |
||||
|
final Intent draft = new Intent(Intent.ACTION_SEND_MULTIPLE); |
||||
|
if (notEmpty(message)) { |
||||
|
Pattern htmlPattern = Pattern.compile(".*\\<[^>]+>.*", Pattern.DOTALL); |
||||
|
if (htmlPattern.matcher(message).matches()) { |
||||
|
draft.putExtra(android.content.Intent.EXTRA_TEXT, Html.fromHtml(message)); |
||||
|
draft.setType("text/html"); |
||||
|
} else { |
||||
|
draft.putExtra(android.content.Intent.EXTRA_TEXT, message); |
||||
|
draft.setType("text/plain"); |
||||
|
} |
||||
|
} |
||||
|
if (notEmpty(subject)) { |
||||
|
draft.putExtra(android.content.Intent.EXTRA_SUBJECT, subject); |
||||
|
} |
||||
|
try { |
||||
|
if (to != null && to.length() > 0) { |
||||
|
draft.putExtra(android.content.Intent.EXTRA_EMAIL, toStringArray(to)); |
||||
|
} |
||||
|
if (cc != null && cc.length() > 0) { |
||||
|
draft.putExtra(android.content.Intent.EXTRA_CC, toStringArray(cc)); |
||||
|
} |
||||
|
if (bcc != null && bcc.length() > 0) { |
||||
|
draft.putExtra(android.content.Intent.EXTRA_BCC, toStringArray(bcc)); |
||||
|
} |
||||
|
if (files.length() > 0) { |
||||
|
final String dir = getDownloadDir(); |
||||
|
if (dir != null) { |
||||
|
ArrayList<Uri> fileUris = new ArrayList<Uri>(); |
||||
|
for (int i = 0; i < files.length(); i++) { |
||||
|
final Uri fileUri = getFileUriAndSetType(draft, dir, files.getString(i), subject, i); |
||||
|
if (fileUri != null) { |
||||
|
fileUris.add(fileUri); |
||||
|
} |
||||
|
} |
||||
|
if (!fileUris.isEmpty()) { |
||||
|
draft.putExtra(Intent.EXTRA_STREAM, fileUris); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
callbackContext.error(e.getMessage()); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// this was added to start the intent in a new window as suggested in #300 to prevent crashes upon return |
||||
|
draft.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
||||
|
|
||||
|
draft.setType("application/octet-stream"); |
||||
|
|
||||
|
// as an experiment for #300 we're explicitly running it on the ui thread here |
||||
|
cordova.getActivity().runOnUiThread(new Runnable() { |
||||
|
public void run() { |
||||
|
cordova.startActivityForResult(plugin, Intent.createChooser(draft, "Choose Email App"), ACTIVITY_CODE_SENDVIAEMAIL); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private String getDownloadDir() throws IOException { |
||||
|
// better check, otherwise it may crash the app |
||||
|
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { |
||||
|
// we need to use external storage since we need to share to another app |
||||
|
final String dir = webView.getContext().getExternalFilesDir(null) + "/socialsharing-downloads"; |
||||
|
createOrCleanDir(dir); |
||||
|
return dir; |
||||
|
} else { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean shareWithOptions(CallbackContext callbackContext, JSONObject jsonObject) { |
||||
|
return doSendIntent( |
||||
|
callbackContext, |
||||
|
jsonObject.optString("message", null), |
||||
|
jsonObject.optString("subject", null), |
||||
|
jsonObject.optJSONArray("files") == null ? new JSONArray() : jsonObject.optJSONArray("files"), |
||||
|
jsonObject.optString("url", null), |
||||
|
null, |
||||
|
jsonObject.optString("chooserTitle", null), |
||||
|
false, |
||||
|
false |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private boolean doSendIntent( |
||||
|
final CallbackContext callbackContext, |
||||
|
final String msg, |
||||
|
final String subject, |
||||
|
final JSONArray files, |
||||
|
final String url, |
||||
|
final String appPackageName, |
||||
|
final String chooserTitle, |
||||
|
final boolean peek, |
||||
|
final boolean boolResult) { |
||||
|
return doSendIntent(callbackContext, msg, subject, files, url, appPackageName, chooserTitle, peek, boolResult, null); |
||||
|
} |
||||
|
|
||||
|
private boolean doSendIntent( |
||||
|
final CallbackContext callbackContext, |
||||
|
final String msg, |
||||
|
final String subject, |
||||
|
final JSONArray files, |
||||
|
final String url, |
||||
|
final String appPackageName, |
||||
|
final String chooserTitle, |
||||
|
final boolean peek, |
||||
|
final boolean boolResult, |
||||
|
final String appName) { |
||||
|
|
||||
|
final CordovaInterface mycordova = cordova; |
||||
|
final CordovaPlugin plugin = this; |
||||
|
|
||||
|
cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) { |
||||
|
public void run() { |
||||
|
String message = msg; |
||||
|
final boolean hasMultipleAttachments = files.length() > 1; |
||||
|
final Intent sendIntent = new Intent(hasMultipleAttachments ? Intent.ACTION_SEND_MULTIPLE : Intent.ACTION_SEND); |
||||
|
sendIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); |
||||
|
|
||||
|
try { |
||||
|
if (files.length() > 0 && !"".equals(files.getString(0))) { |
||||
|
final String dir = getDownloadDir(); |
||||
|
if (dir != null) { |
||||
|
ArrayList<Uri> fileUris = new ArrayList<Uri>(); |
||||
|
Uri fileUri = null; |
||||
|
for (int i = 0; i < files.length(); i++) { |
||||
|
fileUri = getFileUriAndSetType(sendIntent, dir, files.getString(i), subject, i); |
||||
|
fileUri = FileProvider.getUriForFile(webView.getContext(), cordova.getActivity().getPackageName()+".sharing.provider", new File(fileUri.getPath())); |
||||
|
if (fileUri != null) { |
||||
|
fileUris.add(fileUri); |
||||
|
} |
||||
|
} |
||||
|
if (!fileUris.isEmpty()) { |
||||
|
if (hasMultipleAttachments) { |
||||
|
sendIntent.putExtra(Intent.EXTRA_STREAM, fileUris); |
||||
|
} else { |
||||
|
sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
sendIntent.setType("text/plain"); |
||||
|
} |
||||
|
} else { |
||||
|
sendIntent.setType("text/plain"); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
callbackContext.error(e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
if (notEmpty(subject)) { |
||||
|
sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject); |
||||
|
} |
||||
|
|
||||
|
// add the URL to the message, as there seems to be no separate field |
||||
|
if (notEmpty(url)) { |
||||
|
if (notEmpty(message)) { |
||||
|
message += " " + url; |
||||
|
} else { |
||||
|
message = url; |
||||
|
} |
||||
|
} |
||||
|
if (notEmpty(message)) { |
||||
|
sendIntent.putExtra(android.content.Intent.EXTRA_TEXT, message); |
||||
|
// sometimes required when the user picks share via sms |
||||
|
if (Build.VERSION.SDK_INT < 21) { // LOLLIPOP |
||||
|
sendIntent.putExtra("sms_body", message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// this was added to start the intent in a new window as suggested in #300 to prevent crashes upon return |
||||
|
sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
||||
|
|
||||
|
if (appPackageName != null) { |
||||
|
String packageName = appPackageName; |
||||
|
String passedActivityName = null; |
||||
|
if (packageName.contains("/")) { |
||||
|
String[] items = appPackageName.split("/"); |
||||
|
packageName = items[0]; |
||||
|
passedActivityName = items[1]; |
||||
|
} |
||||
|
final ActivityInfo activity = getActivity(callbackContext, sendIntent, packageName, appName); |
||||
|
if (activity != null) { |
||||
|
if (peek) { |
||||
|
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); |
||||
|
} else { |
||||
|
sendIntent.addCategory(Intent.CATEGORY_LAUNCHER); |
||||
|
sendIntent.setComponent(new ComponentName(activity.applicationInfo.packageName, |
||||
|
passedActivityName != null ? passedActivityName : activity.name)); |
||||
|
|
||||
|
// as an experiment for #300 we're explicitly running it on the ui thread here |
||||
|
cordova.getActivity().runOnUiThread(new Runnable() { |
||||
|
public void run() { |
||||
|
mycordova.startActivityForResult(plugin, sendIntent, 0); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (pasteMessage != null) { |
||||
|
// add a little delay because target app (facebook only atm) needs to be started first |
||||
|
new Timer().schedule(new TimerTask() { |
||||
|
public void run() { |
||||
|
cordova.getActivity().runOnUiThread(new Runnable() { |
||||
|
public void run() { |
||||
|
copyHintToClipboard(msg, pasteMessage); |
||||
|
showPasteMessage(pasteMessage); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}, 2000); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
if (peek) { |
||||
|
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); |
||||
|
} else { |
||||
|
// experimenting a bit |
||||
|
// as an experiment for #300 we're explicitly running it on the ui thread here |
||||
|
cordova.getActivity().runOnUiThread(new Runnable() { |
||||
|
public void run() { |
||||
|
mycordova.startActivityForResult(plugin, Intent.createChooser(sendIntent, chooserTitle), boolResult ? ACTIVITY_CODE_SEND__BOOLRESULT : ACTIVITY_CODE_SEND__OBJECT); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@SuppressLint("NewApi") |
||||
|
private void copyHintToClipboard(String msg, String label) { |
||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { |
||||
|
return; |
||||
|
} |
||||
|
final ClipboardManager clipboard = (android.content.ClipboardManager) cordova.getActivity().getSystemService(Context.CLIPBOARD_SERVICE); |
||||
|
final ClipData clip = android.content.ClipData.newPlainText(label, msg); |
||||
|
clipboard.setPrimaryClip(clip); |
||||
|
} |
||||
|
|
||||
|
@SuppressLint("NewApi") |
||||
|
private void showPasteMessage(String label) { |
||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { |
||||
|
return; |
||||
|
} |
||||
|
final Toast toast = Toast.makeText(webView.getContext(), label, Toast.LENGTH_LONG); |
||||
|
toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 0, 0); |
||||
|
toast.show(); |
||||
|
} |
||||
|
|
||||
|
private Uri getFileUriAndSetType(Intent sendIntent, String dir, String image, String subject, int nthFile) throws IOException { |
||||
|
// we're assuming an image, but this can be any filetype you like |
||||
|
String localImage = image; |
||||
|
if (image.endsWith("mp4") || image.endsWith("mov") || image.endsWith("3gp")){ |
||||
|
sendIntent.setType("video/*"); |
||||
|
} else { |
||||
|
sendIntent.setType("image/*"); |
||||
|
} |
||||
|
|
||||
|
if (image.startsWith("http") || image.startsWith("www/")) { |
||||
|
String filename = getFileName(image); |
||||
|
localImage = "file://" + dir + "/" + filename; |
||||
|
if (image.startsWith("http")) { |
||||
|
// filename optimisation taken from https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/pull/56 |
||||
|
URLConnection connection = new URL(image).openConnection(); |
||||
|
String disposition = connection.getHeaderField("Content-Disposition"); |
||||
|
if (disposition != null) { |
||||
|
final Pattern dispositionPattern = Pattern.compile("filename=([^;]+)"); |
||||
|
Matcher matcher = dispositionPattern.matcher(disposition); |
||||
|
if (matcher.find()) { |
||||
|
filename = matcher.group(1).replaceAll("[^a-zA-Z0-9._-]", ""); |
||||
|
if (filename.length() == 0) { |
||||
|
// in this case we can't determine a filetype so some targets (gmail) may not render it correctly |
||||
|
filename = "file"; |
||||
|
} |
||||
|
localImage = "file://" + dir + "/" + filename; |
||||
|
} |
||||
|
} |
||||
|
saveFile(getBytes(connection.getInputStream()), dir, filename); |
||||
|
} else { |
||||
|
saveFile(getBytes(webView.getContext().getAssets().open(image)), dir, filename); |
||||
|
} |
||||
|
} else if (image.startsWith("data:")) { |
||||
|
// safeguard for https://code.google.com/p/android/issues/detail?id=7901#c43 |
||||
|
if (!image.contains(";base64,")) { |
||||
|
sendIntent.setType("text/plain"); |
||||
|
return null; |
||||
|
} |
||||
|
// image looks like this: data:image/png;base64,R0lGODlhDAA... |
||||
|
final String encodedImg = image.substring(image.indexOf(";base64,") + 8); |
||||
|
// correct the intent type if anything else was passed, like a pdf: data:application/pdf;base64,.. |
||||
|
if (!image.contains("data:image/")) { |
||||
|
sendIntent.setType(image.substring(image.indexOf("data:") + 5, image.indexOf(";base64"))); |
||||
|
} |
||||
|
// the filename needs a valid extension, so it renders correctly in target apps |
||||
|
final String imgExtension = image.substring(image.indexOf("/") + 1, image.indexOf(";base64")); |
||||
|
String fileName; |
||||
|
// if a subject was passed, use it as the filename |
||||
|
// filenames must be unique when passing in multiple files [#158] |
||||
|
if (notEmpty(subject)) { |
||||
|
fileName = sanitizeFilename(subject) + (nthFile == 0 ? "" : "_" + nthFile) + "." + imgExtension; |
||||
|
} else { |
||||
|
fileName = "file" + (nthFile == 0 ? "" : "_" + nthFile) + "." + imgExtension; |
||||
|
} |
||||
|
saveFile(Base64.decode(encodedImg, Base64.DEFAULT), dir, fileName); |
||||
|
localImage = "file://" + dir + "/" + fileName; |
||||
|
} else if (image.startsWith("df:")) { |
||||
|
// safeguard for https://code.google.com/p/android/issues/detail?id=7901#c43 |
||||
|
if (!image.contains(";base64,")) { |
||||
|
sendIntent.setType("text/plain"); |
||||
|
return null; |
||||
|
} |
||||
|
// format looks like this : df:filename.txt;data:image/png;base64,R0lGODlhDAA... |
||||
|
final String fileName = image.substring(image.indexOf("df:") + 3, image.indexOf(";data:")); |
||||
|
final String fileType = image.substring(image.indexOf(";data:") + 6, image.indexOf(";base64,")); |
||||
|
final String encodedImg = image.substring(image.indexOf(";base64,") + 8); |
||||
|
sendIntent.setType(fileType); |
||||
|
saveFile(Base64.decode(encodedImg, Base64.DEFAULT), dir, sanitizeFilename(fileName)); |
||||
|
localImage = "file://" + dir + "/" + sanitizeFilename(fileName); |
||||
|
} else if (!image.startsWith("file://")) { |
||||
|
throw new IllegalArgumentException("URL_NOT_SUPPORTED"); |
||||
|
} else { |
||||
|
//get file MIME type |
||||
|
String type = getMIMEType(image); |
||||
|
//set intent data and Type |
||||
|
sendIntent.setType(type); |
||||
|
} |
||||
|
return Uri.parse(localImage); |
||||
|
} |
||||
|
|
||||
|
private String getMIMEType(String fileName) { |
||||
|
String type = "*/*"; |
||||
|
int dotIndex = fileName.lastIndexOf("."); |
||||
|
if (dotIndex == -1) { |
||||
|
return type; |
||||
|
} |
||||
|
final String end = fileName.substring(dotIndex+1, fileName.length()).toLowerCase(); |
||||
|
String fromMap = MIME_Map.get(end); |
||||
|
return fromMap == null ? type : fromMap; |
||||
|
} |
||||
|
|
||||
|
private static final Map<String, String> MIME_Map = new HashMap<String, String>(); |
||||
|
static { |
||||
|
MIME_Map.put("3gp", "video/3gpp"); |
||||
|
MIME_Map.put("apk", "application/vnd.android.package-archive"); |
||||
|
MIME_Map.put("asf", "video/x-ms-asf"); |
||||
|
MIME_Map.put("avi", "video/x-msvideo"); |
||||
|
MIME_Map.put("bin", "application/octet-stream"); |
||||
|
MIME_Map.put("bmp", "image/bmp"); |
||||
|
MIME_Map.put("c", "text/plain"); |
||||
|
MIME_Map.put("class", "application/octet-stream"); |
||||
|
MIME_Map.put("conf", "text/plain"); |
||||
|
MIME_Map.put("cpp", "text/plain"); |
||||
|
MIME_Map.put("doc", "application/msword"); |
||||
|
MIME_Map.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); |
||||
|
MIME_Map.put("xls", "application/vnd.ms-excel"); |
||||
|
MIME_Map.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); |
||||
|
MIME_Map.put("exe", "application/octet-stream"); |
||||
|
MIME_Map.put("gif", "image/gif"); |
||||
|
MIME_Map.put("gtar", "application/x-gtar"); |
||||
|
MIME_Map.put("gz", "application/x-gzip"); |
||||
|
MIME_Map.put("h", "text/plain"); |
||||
|
MIME_Map.put("htm", "text/html"); |
||||
|
MIME_Map.put("html", "text/html"); |
||||
|
MIME_Map.put("jar", "application/java-archive"); |
||||
|
MIME_Map.put("java", "text/plain"); |
||||
|
MIME_Map.put("jpeg", "image/jpeg"); |
||||
|
MIME_Map.put("jpg", "image/*"); |
||||
|
MIME_Map.put("js", "application/x-javascript"); |
||||
|
MIME_Map.put("log", "text/plain"); |
||||
|
MIME_Map.put("m3u", "audio/x-mpegurl"); |
||||
|
MIME_Map.put("m4a", "audio/mp4a-latm"); |
||||
|
MIME_Map.put("m4b", "audio/mp4a-latm"); |
||||
|
MIME_Map.put("m4p", "audio/mp4a-latm"); |
||||
|
MIME_Map.put("m4u", "video/vnd.mpegurl"); |
||||
|
MIME_Map.put("m4v", "video/x-m4v"); |
||||
|
MIME_Map.put("mov", "video/quicktime"); |
||||
|
MIME_Map.put("mp2", "audio/x-mpeg"); |
||||
|
MIME_Map.put("mp3", "audio/x-mpeg"); |
||||
|
MIME_Map.put("mp4", "video/mp4"); |
||||
|
MIME_Map.put("mpc", "application/vnd.mpohun.certificate"); |
||||
|
MIME_Map.put("mpe", "video/mpeg"); |
||||
|
MIME_Map.put("mpeg", "video/mpeg"); |
||||
|
MIME_Map.put("mpg", "video/mpeg"); |
||||
|
MIME_Map.put("mpg4", "video/mp4"); |
||||
|
MIME_Map.put("mpga", "audio/mpeg"); |
||||
|
MIME_Map.put("msg", "application/vnd.ms-outlook"); |
||||
|
MIME_Map.put("ogg", "audio/ogg"); |
||||
|
MIME_Map.put("pdf", "application/pdf"); |
||||
|
MIME_Map.put("png", "image/png"); |
||||
|
MIME_Map.put("pps", "application/vnd.ms-powerpoint"); |
||||
|
MIME_Map.put("ppt", "application/vnd.ms-powerpoint"); |
||||
|
MIME_Map.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"); |
||||
|
MIME_Map.put("prop", "text/plain"); |
||||
|
MIME_Map.put("rc", "text/plain"); |
||||
|
MIME_Map.put("rmvb", "audio/x-pn-realaudio"); |
||||
|
MIME_Map.put("rtf", "application/rtf"); |
||||
|
MIME_Map.put("sh", "text/plain"); |
||||
|
MIME_Map.put("tar", "application/x-tar"); |
||||
|
MIME_Map.put("tgz", "application/x-compressed"); |
||||
|
MIME_Map.put("txt", "text/plain"); |
||||
|
MIME_Map.put("wav", "audio/x-wav"); |
||||
|
MIME_Map.put("wma", "audio/x-ms-wma"); |
||||
|
MIME_Map.put("wmv", "audio/x-ms-wmv"); |
||||
|
MIME_Map.put("wps", "application/vnd.ms-works"); |
||||
|
MIME_Map.put("xml", "text/plain"); |
||||
|
MIME_Map.put("z", "application/x-compress"); |
||||
|
MIME_Map.put("zip", "application/x-zip-compressed"); |
||||
|
MIME_Map.put("", "*/*"); |
||||
|
} |
||||
|
|
||||
|
private boolean shareViaWhatsAppDirectly(final CallbackContext callbackContext, String message, final String subject, final JSONArray files, final String url, final String number) { |
||||
|
// add the URL to the message, as there seems to be no separate field |
||||
|
if (notEmpty(url)) { |
||||
|
if (notEmpty(message)) { |
||||
|
message += " " + url; |
||||
|
} else { |
||||
|
message = url; |
||||
|
} |
||||
|
} |
||||
|
final String shareMessage = message; |
||||
|
final SocialSharing plugin = this; |
||||
|
cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) { |
||||
|
public void run() { |
||||
|
final Intent intent = new Intent(Intent.ACTION_SENDTO); |
||||
|
intent.setData(Uri.parse("smsto:" + number)); |
||||
|
|
||||
|
intent.putExtra("sms_body", shareMessage); |
||||
|
intent.putExtra("sms_subject", subject); |
||||
|
intent.setPackage("com.whatsapp"); |
||||
|
|
||||
|
try { |
||||
|
if (files.length() > 0 && !"".equals(files.getString(0))) { |
||||
|
final boolean hasMultipleAttachments = files.length() > 1; |
||||
|
final String dir = getDownloadDir(); |
||||
|
if (dir != null) { |
||||
|
ArrayList<Uri> fileUris = new ArrayList<Uri>(); |
||||
|
Uri fileUri = null; |
||||
|
for (int i = 0; i < files.length(); i++) { |
||||
|
fileUri = getFileUriAndSetType(intent, dir, files.getString(i), subject, i); |
||||
|
if (fileUri != null) { |
||||
|
fileUris.add(fileUri); |
||||
|
} |
||||
|
} |
||||
|
if (!fileUris.isEmpty()) { |
||||
|
if (hasMultipleAttachments) { |
||||
|
intent.putExtra(Intent.EXTRA_STREAM, fileUris); |
||||
|
} else { |
||||
|
intent.putExtra(Intent.EXTRA_STREAM, fileUri); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
callbackContext.error(e.getMessage()); |
||||
|
} |
||||
|
try { |
||||
|
// this was added to start the intent in a new window as suggested in #300 to prevent crashes upon return |
||||
|
// update: didn't help (doesn't seem to hurt either though) |
||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
||||
|
|
||||
|
// as an experiment for #300 we're explicitly running it on the ui thread here |
||||
|
cordova.getActivity().runOnUiThread(new Runnable() { |
||||
|
public void run() { |
||||
|
cordova.startActivityForResult(plugin, intent, ACTIVITY_CODE_SENDVIAWHATSAPP); |
||||
|
} |
||||
|
}); |
||||
|
} catch (Exception e) { |
||||
|
callbackContext.error(e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private boolean invokeSMSIntent(final CallbackContext callbackContext, JSONObject options, String p_phonenumbers) { |
||||
|
final String message = options.optString("message"); |
||||
|
// TODO test this on a real SMS enabled device before releasing it |
||||
|
// final String subject = options.optString("subject"); |
||||
|
// final String image = options.optString("image"); |
||||
|
final String subject = null; //options.optString("subject"); |
||||
|
final String image = null; // options.optString("image"); |
||||
|
final String phonenumbers = getPhoneNumbersWithManufacturerSpecificSeparators(p_phonenumbers); |
||||
|
final SocialSharing plugin = this; |
||||
|
cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) { |
||||
|
public void run() { |
||||
|
Intent intent; |
||||
|
|
||||
|
if (Build.VERSION.SDK_INT >= 19) { // Build.VERSION_CODES.KITKAT) { |
||||
|
// passing in no phonenumbers for kitkat may result in an error, |
||||
|
// but it may also work for some devices, so documentation will need to cover this case |
||||
|
intent = new Intent(Intent.ACTION_SENDTO); |
||||
|
intent.setData(Uri.parse("smsto:" + (notEmpty(phonenumbers) ? phonenumbers : ""))); |
||||
|
} else { |
||||
|
intent = new Intent(Intent.ACTION_VIEW); |
||||
|
intent.setType("vnd.android-dir/mms-sms"); |
||||
|
if (phonenumbers != null) { |
||||
|
intent.putExtra("address", phonenumbers); |
||||
|
} |
||||
|
} |
||||
|
intent.putExtra("sms_body", message); |
||||
|
intent.putExtra("sms_subject", subject); |
||||
|
|
||||
|
try { |
||||
|
if (image != null && !"".equals(image)) { |
||||
|
final Uri fileUri = getFileUriAndSetType(intent, getDownloadDir(), image, subject, 0); |
||||
|
if (fileUri != null) { |
||||
|
intent.putExtra(Intent.EXTRA_STREAM, fileUri); |
||||
|
} |
||||
|
} |
||||
|
// this was added to start the intent in a new window as suggested in #300 to prevent crashes upon return |
||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
||||
|
|
||||
|
cordova.startActivityForResult(plugin, intent, 0); |
||||
|
} catch (Exception e) { |
||||
|
callbackContext.error(e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private static String getPhoneNumbersWithManufacturerSpecificSeparators(String phonenumbers) { |
||||
|
if (notEmpty(phonenumbers)) { |
||||
|
char separator; |
||||
|
if (android.os.Build.MANUFACTURER.equalsIgnoreCase("samsung")) { |
||||
|
separator = ','; |
||||
|
} else { |
||||
|
separator = ';'; |
||||
|
} |
||||
|
return phonenumbers.replace(';', separator).replace(',', separator); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private ActivityInfo getActivity(final CallbackContext callbackContext, final Intent shareIntent, final String appPackageName, final String appName) { |
||||
|
final PackageManager pm = webView.getContext().getPackageManager(); |
||||
|
List<ResolveInfo> activityList = pm.queryIntentActivities(shareIntent, 0); |
||||
|
for (final ResolveInfo app : activityList) { |
||||
|
if ((app.activityInfo.packageName).contains(appPackageName)) { |
||||
|
if (appName == null || (app.activityInfo.name).contains(appName)) { |
||||
|
return app.activityInfo; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
// no matching app found |
||||
|
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, getShareActivities(activityList))); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
private JSONArray getShareActivities(List<ResolveInfo> activityList) { |
||||
|
List<String> packages = new ArrayList<String>(); |
||||
|
for (final ResolveInfo app : activityList) { |
||||
|
packages.add(app.activityInfo.packageName); |
||||
|
} |
||||
|
return new JSONArray(packages); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onActivityResult(int requestCode, int resultCode, Intent intent) { |
||||
|
super.onActivityResult(requestCode, resultCode, intent); |
||||
|
if (_callbackContext != null) { |
||||
|
switch (requestCode) { |
||||
|
case ACTIVITY_CODE_SEND__BOOLRESULT: |
||||
|
_callbackContext.sendPluginResult(new PluginResult( |
||||
|
PluginResult.Status.OK, |
||||
|
resultCode == Activity.RESULT_OK)); |
||||
|
break; |
||||
|
case ACTIVITY_CODE_SEND__OBJECT: |
||||
|
JSONObject json = new JSONObject(); |
||||
|
try { |
||||
|
json.put("completed", resultCode == Activity.RESULT_OK); |
||||
|
json.put("app", ""); // we need a completely different approach if we want to support this on Android. Idea: https://clickclickclack.wordpress.com/2012/01/03/intercepting-androids-action_send-intents/ |
||||
|
_callbackContext.sendPluginResult(new PluginResult( |
||||
|
PluginResult.Status.OK, |
||||
|
json)); |
||||
|
} catch (JSONException e) { |
||||
|
_callbackContext.error(e.getMessage()); |
||||
|
} |
||||
|
break; |
||||
|
default: |
||||
|
_callbackContext.success(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void createOrCleanDir(final String downloadDir) throws IOException { |
||||
|
final File dir = new File(downloadDir); |
||||
|
if (!dir.exists()) { |
||||
|
if (!dir.mkdirs()) { |
||||
|
throw new IOException("CREATE_DIRS_FAILED"); |
||||
|
} |
||||
|
} else { |
||||
|
cleanupOldFiles(dir); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static String getFileName(String url) { |
||||
|
if (url.endsWith("/")) { |
||||
|
url = url.substring(0, url.length()-1); |
||||
|
} |
||||
|
final String pattern = ".*/([^?#]+)?"; |
||||
|
Pattern r = Pattern.compile(pattern); |
||||
|
Matcher m = r.matcher(url); |
||||
|
if (m.find()) { |
||||
|
return m.group(1); |
||||
|
} else { |
||||
|
return "file"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private byte[] getBytes(InputStream is) throws IOException { |
||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
||||
|
int nRead; |
||||
|
byte[] data = new byte[16384]; |
||||
|
while ((nRead = is.read(data, 0, data.length)) != -1) { |
||||
|
buffer.write(data, 0, nRead); |
||||
|
} |
||||
|
buffer.flush(); |
||||
|
return buffer.toByteArray(); |
||||
|
} |
||||
|
|
||||
|
private void saveFile(byte[] bytes, String dirName, String fileName) throws IOException { |
||||
|
final File dir = new File(dirName); |
||||
|
final FileOutputStream fos = new FileOutputStream(new File(dir, fileName)); |
||||
|
fos.write(bytes); |
||||
|
fos.flush(); |
||||
|
fos.close(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* As file.deleteOnExit does not work on Android, we need to delete files manually. |
||||
|
* Deleting them in onActivityResult is not a good idea, because for example a base64 encoded file |
||||
|
* will not be available for upload to Facebook (it's deleted before it's uploaded). |
||||
|
* So the best approach is deleting old files when saving (sharing) a new one. |
||||
|
*/ |
||||
|
private void cleanupOldFiles(File dir) { |
||||
|
for (File f : dir.listFiles()) { |
||||
|
//noinspection ResultOfMethodCallIgnored |
||||
|
f.delete(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static boolean notEmpty(String what) { |
||||
|
return what != null && |
||||
|
!"".equals(what) && |
||||
|
!"null".equalsIgnoreCase(what); |
||||
|
} |
||||
|
|
||||
|
private static String[] toStringArray(JSONArray jsonArray) throws JSONException { |
||||
|
String[] result = new String[jsonArray.length()]; |
||||
|
for (int i = 0; i < jsonArray.length(); i++) { |
||||
|
result[i] = jsonArray.getString(i); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static String sanitizeFilename(String name) { |
||||
|
return name.replaceAll("[:\\\\/*?|<> ]", "_"); |
||||
|
} |
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<paths> |
||||
|
<external-path |
||||
|
name="Android/data/${applicationId}/socialsharing_downloads" |
||||
|
path="./socialsharing-downloads"/> |
||||
|
|
||||
|
<root-path |
||||
|
name="root" |
||||
|
path="/"/> |
||||
|
|
||||
|
</paths> |
@ -0,0 +1,5 @@ |
|||||
|
#import <Foundation/Foundation.h> |
||||
|
|
||||
|
@interface NSString (URLEncoding) |
||||
|
@property (readonly) NSString *URLEncodedString; |
||||
|
@end |
@ -0,0 +1,30 @@ |
|||||
|
#import "NSString+URLEncoding.h" |
||||
|
|
||||
|
@implementation NSString (URLEncoding) |
||||
|
- (NSString*)URLEncodedString |
||||
|
{ |
||||
|
NSString* result = (NSString *)CFBridgingRelease( |
||||
|
CFURLCreateStringByAddingPercentEscapes( |
||||
|
kCFAllocatorDefault, |
||||
|
(CFStringRef)self, |
||||
|
CFSTR("#%"), // don't escape these |
||||
|
NULL, // allow escaping these |
||||
|
kCFStringEncodingUTF8 |
||||
|
) |
||||
|
); |
||||
|
|
||||
|
// we may have a URL with more than one '#' now - which iOS doesn't allow, so escape all but the first one |
||||
|
NSArray *parts = [result componentsSeparatedByString:@"#"]; |
||||
|
NSString *finalResult = parts[0]; |
||||
|
for (int i=1; i<parts.count; i++) { |
||||
|
NSString *part = [parts objectAtIndex:i]; |
||||
|
if (i==1) { |
||||
|
finalResult = [finalResult stringByAppendingString:@"#"]; |
||||
|
} else { |
||||
|
finalResult = [finalResult stringByAppendingString:@"%23"]; |
||||
|
} |
||||
|
finalResult = [finalResult stringByAppendingString:part]; |
||||
|
} |
||||
|
return finalResult; |
||||
|
} |
||||
|
@end |
@ -0,0 +1,28 @@ |
|||||
|
#import <Cordova/CDV.h> |
||||
|
#import <MessageUI/MFMailComposeViewController.h> |
||||
|
|
||||
|
@interface SocialSharing : CDVPlugin <UIPopoverControllerDelegate, MFMailComposeViewControllerDelegate, UIDocumentInteractionControllerDelegate> |
||||
|
|
||||
|
@property (nonatomic, strong) MFMailComposeViewController *globalMailComposer; |
||||
|
@property (nonatomic, strong) UIDocumentInteractionController * documentInteractionController; |
||||
|
@property (retain) NSString * tempStoredFile; |
||||
|
@property (retain) CDVInvokedUrlCommand * command; |
||||
|
|
||||
|
- (void)available:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)setIPadPopupCoordinates:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)share:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)shareWithOptions:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)canShareVia:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)canShareViaEmail:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)shareVia:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)shareViaTwitter:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)shareViaFacebook:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)shareViaFacebookWithPasteMessageHint:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)shareViaSMS:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)shareViaEmail:(CDVInvokedUrlCommand*)command; |
||||
|
- (void)shareViaInstagram:(CDVInvokedUrlCommand*)command; |
||||
|
|
||||
|
- (void)saveToPhotoAlbum:(CDVInvokedUrlCommand*)command; |
||||
|
|
||||
|
@end |
@ -0,0 +1,808 @@ |
|||||
|
#import "SocialSharing.h" |
||||
|
#import "NSString+URLEncoding.h" |
||||
|
#import <Cordova/CDV.h> |
||||
|
#import <Social/Social.h> |
||||
|
#import <Foundation/NSException.h> |
||||
|
#import <MessageUI/MFMessageComposeViewController.h> |
||||
|
#import <MessageUI/MFMailComposeViewController.h> |
||||
|
#import <MobileCoreServices/MobileCoreServices.h> |
||||
|
|
||||
|
static NSString *const kShareOptionMessage = @"message"; |
||||
|
static NSString *const kShareOptionSubject = @"subject"; |
||||
|
static NSString *const kShareOptionFiles = @"files"; |
||||
|
static NSString *const kShareOptionUrl = @"url"; |
||||
|
|
||||
|
@implementation SocialSharing { |
||||
|
UIPopoverController *_popover; |
||||
|
NSString *_popupCoordinates; |
||||
|
} |
||||
|
|
||||
|
- (void)pluginInitialize { |
||||
|
if ([self isEmailAvailable]) { |
||||
|
[self cycleTheGlobalMailComposer]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void)available:(CDVInvokedUrlCommand*)command { |
||||
|
BOOL avail = NO; |
||||
|
if (NSClassFromString(@"UIActivityViewController")) { |
||||
|
avail = YES; |
||||
|
} |
||||
|
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:avail]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
} |
||||
|
|
||||
|
- (NSString*)getIPadPopupCoordinates { |
||||
|
if (_popupCoordinates != nil) { |
||||
|
return _popupCoordinates; |
||||
|
} |
||||
|
if ([self.webView respondsToSelector:@selector(stringByEvaluatingJavaScriptFromString:)]) { |
||||
|
return [(UIWebView*)self.webView stringByEvaluatingJavaScriptFromString:@"window.plugins.socialsharing.iPadPopupCoordinates();"]; |
||||
|
} else { |
||||
|
// prolly a wkwebview, ignoring for now |
||||
|
return nil; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void)setIPadPopupCoordinates:(CDVInvokedUrlCommand*)command { |
||||
|
_popupCoordinates = [command.arguments objectAtIndex:0]; |
||||
|
} |
||||
|
|
||||
|
- (CGRect)getPopupRectFromIPadPopupCoordinates:(NSArray*)comps { |
||||
|
CGRect rect = CGRectZero; |
||||
|
if ([comps count] == 4) { |
||||
|
rect = CGRectMake([[comps objectAtIndex:0] integerValue], [[comps objectAtIndex:1] integerValue], [[comps objectAtIndex:2] integerValue], [[comps objectAtIndex:3] integerValue]); |
||||
|
} |
||||
|
return rect; |
||||
|
} |
||||
|
|
||||
|
- (void)share:(CDVInvokedUrlCommand*)command { |
||||
|
[self shareInternal:command |
||||
|
withOptions:@{ |
||||
|
kShareOptionMessage: [command.arguments objectAtIndex:0], |
||||
|
kShareOptionSubject: [command.arguments objectAtIndex:1], |
||||
|
kShareOptionFiles: [command.arguments objectAtIndex:2], |
||||
|
kShareOptionUrl: [command.arguments objectAtIndex:3] |
||||
|
} |
||||
|
isBooleanResponse:YES |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
- (void)shareWithOptions:(CDVInvokedUrlCommand*)command { |
||||
|
NSDictionary* options = [command.arguments objectAtIndex:0]; |
||||
|
[self shareInternal:command |
||||
|
withOptions:options |
||||
|
isBooleanResponse:NO |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
- (void)shareInternal:(CDVInvokedUrlCommand*)command withOptions:(NSDictionary*)options isBooleanResponse:(BOOL)boolResponse { |
||||
|
[self.commandDelegate runInBackground:^{ //avoid main thread block especially if sharing big files from url |
||||
|
if (!NSClassFromString(@"UIActivityViewController")) { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
NSString *message = options[kShareOptionMessage]; |
||||
|
NSString *subject = options[kShareOptionSubject]; |
||||
|
NSArray *filenames = options[kShareOptionFiles]; |
||||
|
NSString *urlString = options[kShareOptionUrl]; |
||||
|
|
||||
|
NSMutableArray *activityItems = [[NSMutableArray alloc] init]; |
||||
|
|
||||
|
if (message != (id)[NSNull null] && message != nil) { |
||||
|
[activityItems addObject:message]; |
||||
|
} |
||||
|
|
||||
|
if (filenames != (id)[NSNull null] && filenames != nil && filenames.count > 0) { |
||||
|
NSMutableArray *files = [[NSMutableArray alloc] init]; |
||||
|
for (NSString* filename in filenames) { |
||||
|
NSObject *file = [self getImage:filename]; |
||||
|
if (file == nil) { |
||||
|
file = [self getFile:filename]; |
||||
|
} |
||||
|
if (file != nil) { |
||||
|
[files addObject:file]; |
||||
|
} |
||||
|
} |
||||
|
[activityItems addObjectsFromArray:files]; |
||||
|
} |
||||
|
|
||||
|
if (urlString != (id)[NSNull null] && urlString != nil) { |
||||
|
[activityItems addObject:[NSURL URLWithString:[urlString URLEncodedString]]]; |
||||
|
} |
||||
|
|
||||
|
UIActivity *activity = [[UIActivity alloc] init]; |
||||
|
NSArray *applicationActivities = [[NSArray alloc] initWithObjects:activity, nil]; |
||||
|
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:applicationActivities]; |
||||
|
if (subject != (id)[NSNull null] && subject != nil) { |
||||
|
[activityVC setValue:subject forKey:@"subject"]; |
||||
|
} |
||||
|
|
||||
|
if ([activityVC respondsToSelector:(@selector(setCompletionWithItemsHandler:))]) { |
||||
|
[activityVC setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray * returnedItems, NSError * activityError) { |
||||
|
[self cleanupStoredFiles]; |
||||
|
if (boolResponse) { |
||||
|
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:completed] |
||||
|
callbackId:command.callbackId]; |
||||
|
} else { |
||||
|
NSDictionary * result = @{@"completed":@(completed), @"app":activityType == nil ? @"" : activityType}; |
||||
|
[self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result] |
||||
|
callbackId:command.callbackId]; |
||||
|
} |
||||
|
}]; |
||||
|
} else { |
||||
|
// let's suppress this warning otherwise folks will start opening issues while it's not relevant |
||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" |
||||
|
[activityVC setCompletionHandler:^(NSString *activityType, BOOL completed) { |
||||
|
[self cleanupStoredFiles]; |
||||
|
NSDictionary * result = @{@"completed":@(completed), @"app":activityType}; |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
}]; |
||||
|
#pragma GCC diagnostic warning "-Wdeprecated-declarations" |
||||
|
} |
||||
|
|
||||
|
NSArray * socialSharingExcludeActivities = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"SocialSharingExcludeActivities"]; |
||||
|
if (socialSharingExcludeActivities!=nil && [socialSharingExcludeActivities count] > 0) { |
||||
|
activityVC.excludedActivityTypes = socialSharingExcludeActivities; |
||||
|
} |
||||
|
|
||||
|
dispatch_async(dispatch_get_main_queue(), ^(void){ |
||||
|
// iPad on iOS >= 8 needs a different approach |
||||
|
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) { |
||||
|
NSString* iPadCoords = [self getIPadPopupCoordinates]; |
||||
|
if (iPadCoords != nil && ![iPadCoords isEqual:@"-1,-1,-1,-1"]) { |
||||
|
NSArray *comps = [iPadCoords componentsSeparatedByString:@","]; |
||||
|
CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps]; |
||||
|
if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) { |
||||
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported |
||||
|
activityVC.popoverPresentationController.sourceView = self.webView; |
||||
|
activityVC.popoverPresentationController.sourceRect = rect; |
||||
|
#endif |
||||
|
} else { |
||||
|
_popover = [[UIPopoverController alloc] initWithContentViewController:activityVC]; |
||||
|
_popover.delegate = self; |
||||
|
[_popover presentPopoverFromRect:rect inView:self.webView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; |
||||
|
} |
||||
|
} else if ([activityVC respondsToSelector:@selector(popoverPresentationController)]) { |
||||
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 // iOS 8.0 supported |
||||
|
activityVC.popoverPresentationController.sourceView = self.webView; |
||||
|
// position the popup at the bottom, just like iOS < 8 did (and iPhone still does on iOS 8) |
||||
|
NSArray *comps = [NSArray arrayWithObjects: |
||||
|
[NSNumber numberWithInt:(self.viewController.view.frame.size.width/2)-200], |
||||
|
[NSNumber numberWithInt:self.viewController.view.frame.size.height], |
||||
|
[NSNumber numberWithInt:400], |
||||
|
[NSNumber numberWithInt:400], |
||||
|
nil]; |
||||
|
CGRect rect = [self getPopupRectFromIPadPopupCoordinates:comps]; |
||||
|
activityVC.popoverPresentationController.sourceRect = rect; |
||||
|
#endif |
||||
|
} |
||||
|
} |
||||
|
[[self getTopMostViewController] presentViewController:activityVC animated:YES completion:nil]; |
||||
|
}); |
||||
|
}]; |
||||
|
} |
||||
|
|
||||
|
- (void)shareViaTwitter:(CDVInvokedUrlCommand*)command { |
||||
|
[self shareViaInternal:command type:SLServiceTypeTwitter]; |
||||
|
} |
||||
|
|
||||
|
- (void)shareViaFacebook:(CDVInvokedUrlCommand*)command { |
||||
|
[self shareViaInternal:command type:SLServiceTypeFacebook]; |
||||
|
} |
||||
|
|
||||
|
- (void)shareViaFacebookWithPasteMessageHint:(CDVInvokedUrlCommand*)command { |
||||
|
// If Fb app is installed a message is not prefilled. |
||||
|
// When shared through the default iOS widget (iOS Settings > Facebook) the message is prefilled already. |
||||
|
NSString *message = [command.arguments objectAtIndex:0]; |
||||
|
if (message != (id)[NSNull null]) { |
||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1000 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{ |
||||
|
BOOL fbAppInstalled = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"fb://"]]; // requires whitelisting on iOS9 |
||||
|
if (fbAppInstalled) { |
||||
|
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; |
||||
|
[pasteboard setValue:message forPasteboardType:@"public.text"]; |
||||
|
NSString *hint = [command.arguments objectAtIndex:4]; |
||||
|
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:hint delegate:nil cancelButtonTitle:nil otherButtonTitles:nil]; |
||||
|
[alert show]; |
||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2800 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{ |
||||
|
[alert dismissWithClickedButtonIndex:-1 animated:YES]; |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
[self shareViaInternal:command type:SLServiceTypeFacebook]; |
||||
|
} |
||||
|
|
||||
|
- (void)shareVia:(CDVInvokedUrlCommand*)command { |
||||
|
[self shareViaInternal:command type:[command.arguments objectAtIndex:4]]; |
||||
|
} |
||||
|
|
||||
|
- (void)canShareVia:(CDVInvokedUrlCommand*)command { |
||||
|
NSString *via = [command.arguments objectAtIndex:4]; |
||||
|
CDVPluginResult * pluginResult; |
||||
|
if ([@"sms" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaSMS]) { |
||||
|
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
||||
|
} else if ([@"email" caseInsensitiveCompare:via] == NSOrderedSame && [self isEmailAvailable]) { |
||||
|
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
||||
|
} else if ([@"whatsapp" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaWhatsApp]) { |
||||
|
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
||||
|
} else if ([@"instagram" caseInsensitiveCompare:via] == NSOrderedSame && [self canShareViaInstagram]) { |
||||
|
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
||||
|
} else if ([self isAvailableForSharing:command type:via]) { |
||||
|
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
||||
|
} else { |
||||
|
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; |
||||
|
} |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
} |
||||
|
|
||||
|
- (void)canShareViaEmail:(CDVInvokedUrlCommand*)command { |
||||
|
if ([self isEmailAvailable]) { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
} else { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (bool)isEmailAvailable { |
||||
|
Class messageClass = (NSClassFromString(@"MFMailComposeViewController")); |
||||
|
return messageClass != nil && [messageClass canSendMail]; |
||||
|
} |
||||
|
|
||||
|
- (bool)isAvailableForSharing:(CDVInvokedUrlCommand*)command |
||||
|
type:(NSString *) type { |
||||
|
// isAvailableForServiceType returns true if you pass it a type that is not |
||||
|
// in the defined constants, this is probably a bug on apples part |
||||
|
if(!([type isEqualToString:SLServiceTypeFacebook] |
||||
|
|| [type isEqualToString:SLServiceTypeTwitter] |
||||
|
|| [type isEqualToString:SLServiceTypeTencentWeibo] |
||||
|
|| [type isEqualToString:SLServiceTypeSinaWeibo])) { |
||||
|
return false; |
||||
|
} |
||||
|
// wrapped in try-catch, because isAvailableForServiceType may crash if an invalid type is passed |
||||
|
@try { |
||||
|
return [SLComposeViewController isAvailableForServiceType:type]; |
||||
|
} |
||||
|
@catch (NSException* exception) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void)shareViaInternal:(CDVInvokedUrlCommand*)command |
||||
|
type:(NSString *) type { |
||||
|
|
||||
|
NSString *message = [command.arguments objectAtIndex:0]; |
||||
|
// subject is not supported by the SLComposeViewController |
||||
|
NSArray *filenames = [command.arguments objectAtIndex:2]; |
||||
|
NSString *urlString = [command.arguments objectAtIndex:3]; |
||||
|
|
||||
|
// boldly invoke the target app, because the phone will display a nice message asking to configure the app |
||||
|
SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:type]; |
||||
|
if (message != (id)[NSNull null]) { |
||||
|
[composeViewController setInitialText:message]; |
||||
|
} |
||||
|
|
||||
|
for (NSString* filename in filenames) { |
||||
|
UIImage* image = [self getImage:filename]; |
||||
|
if (image != nil) { |
||||
|
[composeViewController addImage:image]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (urlString != (id)[NSNull null]) { |
||||
|
[composeViewController addURL:[NSURL URLWithString:[urlString URLEncodedString]]]; |
||||
|
} |
||||
|
|
||||
|
[composeViewController setCompletionHandler:^(SLComposeViewControllerResult result) { |
||||
|
if (SLComposeViewControllerResultCancelled == result) { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"cancelled"]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
} else if ([self isAvailableForSharing:command type:type]) { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:SLComposeViewControllerResultDone == result]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
} else { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
} |
||||
|
// required for iOS6 (issues #162 and #167) |
||||
|
[self.viewController dismissViewControllerAnimated:YES completion:nil]; |
||||
|
}]; |
||||
|
[[self getTopMostViewController] presentViewController:composeViewController animated:YES completion:nil]; |
||||
|
} |
||||
|
|
||||
|
- (void)shareViaEmail:(CDVInvokedUrlCommand*)command { |
||||
|
if ([self isEmailAvailable]) { |
||||
|
|
||||
|
if (TARGET_IPHONE_SIMULATOR && IsAtLeastiOSVersion(@"8.0")) { |
||||
|
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"SocialSharing plugin" |
||||
|
message:@"Sharing via email is not supported on the iOS 8 simulator." |
||||
|
delegate:nil |
||||
|
cancelButtonTitle:@"OK" |
||||
|
otherButtonTitles:nil]; |
||||
|
[alert show]; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
[self cycleTheGlobalMailComposer]; |
||||
|
|
||||
|
self.globalMailComposer.mailComposeDelegate = self; |
||||
|
|
||||
|
if ([command.arguments objectAtIndex:0] != (id)[NSNull null]) { |
||||
|
NSString *message = [command.arguments objectAtIndex:0]; |
||||
|
BOOL isHTML = [message rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch].location != NSNotFound; |
||||
|
[self.globalMailComposer setMessageBody:message isHTML:isHTML]; |
||||
|
} |
||||
|
|
||||
|
if ([command.arguments objectAtIndex:1] != (id)[NSNull null]) { |
||||
|
[self.globalMailComposer setSubject: [command.arguments objectAtIndex:1]]; |
||||
|
} |
||||
|
|
||||
|
if ([command.arguments objectAtIndex:2] != (id)[NSNull null]) { |
||||
|
[self.globalMailComposer setToRecipients:[command.arguments objectAtIndex:2]]; |
||||
|
} |
||||
|
|
||||
|
if ([command.arguments objectAtIndex:3] != (id)[NSNull null]) { |
||||
|
[self.globalMailComposer setCcRecipients:[command.arguments objectAtIndex:3]]; |
||||
|
} |
||||
|
|
||||
|
if ([command.arguments objectAtIndex:4] != (id)[NSNull null]) { |
||||
|
[self.globalMailComposer setBccRecipients:[command.arguments objectAtIndex:4]]; |
||||
|
} |
||||
|
|
||||
|
if ([command.arguments objectAtIndex:5] != (id)[NSNull null]) { |
||||
|
NSArray* attachments = [command.arguments objectAtIndex:5]; |
||||
|
NSFileManager* fileManager = [NSFileManager defaultManager]; |
||||
|
for (NSString* path in attachments) { |
||||
|
NSURL *file = [self getFile:path]; |
||||
|
NSData* data = [fileManager contentsAtPath:file.path]; |
||||
|
|
||||
|
if (!data) { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"invalid attachment"]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
NSString* fileName; |
||||
|
NSString* mimeType; |
||||
|
NSString* basename = [self getBasenameFromAttachmentPath:path]; |
||||
|
|
||||
|
//Find data anywhere in string |
||||
|
NSRange rangeData = [basename rangeOfString:@"data:"]; |
||||
|
if (rangeData.location == NSNotFound) |
||||
|
{ |
||||
|
fileName = [basename pathComponents].lastObject; |
||||
|
mimeType = [self getMimeTypeFromFileExtension:[basename pathExtension]]; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
mimeType = (NSString*)[[[basename substringFromIndex:rangeData.location+rangeData.length] componentsSeparatedByString: @";"] objectAtIndex:0]; |
||||
|
|
||||
|
//Find df anywhere in string |
||||
|
NSRange rangeDF = [basename rangeOfString:@"df:"]; |
||||
|
//If not found fallback to default name |
||||
|
if (rangeDF.location == NSNotFound) { |
||||
|
fileName = @"attachment."; |
||||
|
fileName = [fileName stringByAppendingString:(NSString*)[[mimeType componentsSeparatedByString: @"/"] lastObject]]; |
||||
|
} else { |
||||
|
//Found, apply name |
||||
|
fileName = (NSString*)[[[basename substringFromIndex:rangeDF.location+rangeDF.length] componentsSeparatedByString: @";"] objectAtIndex:0]; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
NSString *base64content = (NSString*)[[basename componentsSeparatedByString: @","] lastObject]; |
||||
|
data = [SocialSharing dataFromBase64String:base64content]; |
||||
|
} |
||||
|
[self.globalMailComposer addAttachmentData:data mimeType:mimeType fileName:fileName]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// remember the command, because we need it in the didFinishWithResult method |
||||
|
_command = command; |
||||
|
|
||||
|
[self.commandDelegate runInBackground:^{ |
||||
|
[[self getTopMostViewController] presentViewController:self.globalMailComposer animated:YES completion:nil]; |
||||
|
}]; |
||||
|
|
||||
|
} else { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (UIViewController*) getTopMostViewController { |
||||
|
UIViewController *presentingViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController; |
||||
|
while (presentingViewController.presentedViewController != nil) { |
||||
|
presentingViewController = presentingViewController.presentedViewController; |
||||
|
} |
||||
|
return presentingViewController; |
||||
|
} |
||||
|
|
||||
|
- (NSString*) getBasenameFromAttachmentPath:(NSString*)path { |
||||
|
if ([path hasPrefix:@"base64:"]) { |
||||
|
NSString* pathWithoutPrefix = [path stringByReplacingOccurrencesOfString:@"base64:" withString:@""]; |
||||
|
return [pathWithoutPrefix substringToIndex:[pathWithoutPrefix rangeOfString:@"//"].location]; |
||||
|
} |
||||
|
return [path componentsSeparatedByString: @"?"][0]; |
||||
|
} |
||||
|
|
||||
|
- (NSString*) getMimeTypeFromFileExtension:(NSString*)extension { |
||||
|
if (!extension) { |
||||
|
return nil; |
||||
|
} |
||||
|
// Get the UTI from the file's extension |
||||
|
CFStringRef ext = (CFStringRef)CFBridgingRetain(extension); |
||||
|
CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, NULL); |
||||
|
// Converting UTI to a mime type |
||||
|
NSString *result = (NSString*)CFBridgingRelease(UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType)); |
||||
|
CFRelease(ext); |
||||
|
CFRelease(type); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Delegate will be called after the mail composer did finish an action |
||||
|
* to dismiss the view. |
||||
|
*/ |
||||
|
- (void) mailComposeController:(MFMailComposeViewController*)controller |
||||
|
didFinishWithResult:(MFMailComposeResult)result |
||||
|
error:(NSError*)error { |
||||
|
bool ok = result == MFMailComposeResultSent; |
||||
|
[self.globalMailComposer dismissViewControllerAnimated:YES completion:^{[self cycleTheGlobalMailComposer];}]; |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId]; |
||||
|
} |
||||
|
|
||||
|
-(void)cycleTheGlobalMailComposer { |
||||
|
// we are cycling the damned GlobalMailComposer: http://stackoverflow.com/questions/25604552/i-have-real-misunderstanding-with-mfmailcomposeviewcontroller-in-swift-ios8-in/25604976#25604976 |
||||
|
self.globalMailComposer = nil; |
||||
|
self.globalMailComposer = [[MFMailComposeViewController alloc] init]; |
||||
|
} |
||||
|
|
||||
|
- (bool)canShareViaSMS { |
||||
|
Class messageClass = (NSClassFromString(@"MFMessageComposeViewController")); |
||||
|
return messageClass != nil && [messageClass canSendText]; |
||||
|
} |
||||
|
|
||||
|
- (void)shareViaSMS:(CDVInvokedUrlCommand*)command { |
||||
|
if ([self canShareViaSMS]) { |
||||
|
NSDictionary* options = [command.arguments objectAtIndex:0]; |
||||
|
NSString *phonenumbers = [command.arguments objectAtIndex:1]; |
||||
|
NSString *message = [options objectForKey:@"message"]; |
||||
|
NSString *subject = [options objectForKey:@"subject"]; |
||||
|
NSString *image = [options objectForKey:@"image"]; |
||||
|
|
||||
|
MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init]; |
||||
|
picker.messageComposeDelegate = (id) self; |
||||
|
if (message != (id)[NSNull null]) { |
||||
|
picker.body = message; |
||||
|
} |
||||
|
if (subject != (id)[NSNull null]) { |
||||
|
[picker setSubject:subject]; |
||||
|
} |
||||
|
if (image != nil && image != (id)[NSNull null]) { |
||||
|
BOOL canSendAttachments = [[MFMessageComposeViewController class] respondsToSelector:@selector(canSendAttachments)]; |
||||
|
if (canSendAttachments) { |
||||
|
NSURL *file = [self getFile:image]; |
||||
|
if (file != nil) { |
||||
|
[picker addAttachmentURL:file withAlternateFilename:nil]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (phonenumbers != (id)[NSNull null]) { |
||||
|
[picker setRecipients:[phonenumbers componentsSeparatedByString:@","]]; |
||||
|
} |
||||
|
// remember the command, because we need it in the didFinishWithResult method |
||||
|
_command = command; |
||||
|
[self.commandDelegate runInBackground:^{ |
||||
|
picker.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; |
||||
|
[[self getTopMostViewController] presentViewController:picker animated:NO completion:nil]; |
||||
|
}]; |
||||
|
} else { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Dismisses the SMS composition interface when users taps Cancel or Send |
||||
|
- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result { |
||||
|
bool ok = result == MessageComposeResultSent; |
||||
|
[[self getTopMostViewController] dismissViewControllerAnimated:YES completion:nil]; |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:ok]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:_command.callbackId]; |
||||
|
} |
||||
|
|
||||
|
- (bool)canShareViaInstagram { |
||||
|
return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"instagram://app"]]; // requires whitelisting on iOS9 |
||||
|
} |
||||
|
|
||||
|
- (bool)canShareViaWhatsApp { |
||||
|
return [[UIApplication sharedApplication] canOpenURL: [NSURL URLWithString:@"whatsapp://app"]]; // requires whitelisting on iOS9 |
||||
|
} |
||||
|
|
||||
|
// this is only an internal test method for now, can be used to open a share sheet with 'Open in xx' links for tumblr, drive, dropbox, .. |
||||
|
- (void)openImage:(NSString *)imageName { |
||||
|
UIImage* image =[self getImage:imageName]; |
||||
|
if (image != nil) { |
||||
|
NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/myTempImage.jpg"]; |
||||
|
[UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES]; |
||||
|
_documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]]; |
||||
|
_documentInteractionController.UTI = @""; // TODO find the scheme for google drive and create a shareViaGoogleDrive function |
||||
|
[_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void)shareViaInstagram:(CDVInvokedUrlCommand*)command { |
||||
|
|
||||
|
// on iOS9 canShareVia('instagram'..) will only work if instagram:// is whitelisted. |
||||
|
// If it's not, this method will ask permission to the user on iOS9 for opening the app, |
||||
|
// which is of course better than Instagram sharing not working at all because you forgot to whitelist it. |
||||
|
// Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('instagram'..) |
||||
|
if (!IsAtLeastiOSVersion(@"9.0")) { |
||||
|
if (![self canShareViaInstagram]) { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
NSString *message = [command.arguments objectAtIndex:0]; |
||||
|
// subject is not supported by the SLComposeViewController |
||||
|
NSArray *filenames = [command.arguments objectAtIndex:2]; |
||||
|
|
||||
|
// only use the first image (for now.. maybe we can share in a loop?) |
||||
|
UIImage* image = nil; |
||||
|
for (NSString* filename in filenames) { |
||||
|
image = [self getImage:filename]; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
// NSData *imageObj = [NSData dataFromBase64String:objectAtIndex0]; |
||||
|
NSString *tmpDir = NSTemporaryDirectory(); |
||||
|
NSString *path = [tmpDir stringByAppendingPathComponent:@"instagram.igo"]; |
||||
|
[UIImageJPEGRepresentation(image, 1.0) writeToFile:path atomically:YES]; |
||||
|
|
||||
|
_documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:path]]; |
||||
|
_documentInteractionController.delegate = self; |
||||
|
_documentInteractionController.UTI = @"com.instagram.exclusivegram"; |
||||
|
|
||||
|
if (message != (id)[NSNull null]) { |
||||
|
// no longer working, so .. |
||||
|
_documentInteractionController.annotation = @{@"InstagramCaption" : message}; |
||||
|
|
||||
|
// .. we put the message on the clipboard (you app can prompt the user to paste it) |
||||
|
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; |
||||
|
[pasteboard setValue:message forPasteboardType:@"public.text"]; |
||||
|
} |
||||
|
|
||||
|
// remember the command for the delegate method |
||||
|
_command = command; |
||||
|
|
||||
|
// test for #513 |
||||
|
dispatch_async(dispatch_get_main_queue(), ^(void){ |
||||
|
[_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.webView animated:YES]; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
- (void)shareViaWhatsApp:(CDVInvokedUrlCommand*)command { |
||||
|
|
||||
|
// on iOS9 canShareVia('whatsapp'..) will only work if whatsapp:// is whitelisted. |
||||
|
// If it's not, this method will ask permission to the user on iOS9 for opening the app, |
||||
|
// which is of course better than WhatsApp sharing not working at all because you forgot to whitelist it. |
||||
|
// Tradeoff: on iOS9 this method will always return true, so make sure to whitelist it and call canShareVia('whatsapp'..) |
||||
|
if (!IsAtLeastiOSVersion(@"9.0")) { |
||||
|
if (![self canShareViaWhatsApp]) { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"not available"]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
NSString *message = [command.arguments objectAtIndex:0]; |
||||
|
// subject is not supported by the SLComposeViewController |
||||
|
NSArray *filenames = [command.arguments objectAtIndex:2]; |
||||
|
NSString *urlString = [command.arguments objectAtIndex:3]; |
||||
|
NSString *abid = [command.arguments objectAtIndex:4]; |
||||
|
|
||||
|
// only use the first image (for now.. maybe we can share in a loop?) |
||||
|
UIImage* image = nil; |
||||
|
for (NSString* filename in filenames) { |
||||
|
image = [self getImage:filename]; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
// with WhatsApp, we can share an image OR text+url.. image wins if set |
||||
|
if (image != nil) { |
||||
|
NSString * savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/whatsAppTmp.wai"]; |
||||
|
[UIImageJPEGRepresentation(image, 1.0) writeToFile:savePath atomically:YES]; |
||||
|
_documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:savePath]]; |
||||
|
_documentInteractionController.UTI = @"net.whatsapp.image"; |
||||
|
_documentInteractionController.delegate = self; |
||||
|
_command = command; |
||||
|
[_documentInteractionController presentOpenInMenuFromRect:CGRectZero inView:self.viewController.view animated: YES]; |
||||
|
} else { |
||||
|
// append an url to a message, if both are passed |
||||
|
NSString * shareString = @""; |
||||
|
if (message != (id)[NSNull null]) { |
||||
|
shareString = message; |
||||
|
} |
||||
|
if (urlString != (id)[NSNull null]) { |
||||
|
if ([shareString isEqual: @""]) { |
||||
|
shareString = urlString; |
||||
|
} else { |
||||
|
shareString = [NSString stringWithFormat:@"%@ %@", shareString, [urlString URLEncodedString]]; |
||||
|
} |
||||
|
} |
||||
|
NSString * encodedShareString = [shareString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; |
||||
|
// also encode the '=' character |
||||
|
encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"]; |
||||
|
encodedShareString = [encodedShareString stringByReplacingOccurrencesOfString:@"&" withString:@"%26"]; |
||||
|
NSString * abidString = @""; |
||||
|
if (abid != (id)[NSNull null]) { |
||||
|
abidString = [NSString stringWithFormat:@"abid=%@&", abid]; |
||||
|
} |
||||
|
NSString * encodedShareStringForWhatsApp = [NSString stringWithFormat:@"whatsapp://send?%@text=%@", abidString, encodedShareString]; |
||||
|
|
||||
|
NSURL *whatsappURL = [NSURL URLWithString:encodedShareStringForWhatsApp]; |
||||
|
[[UIApplication sharedApplication] openURL: whatsappURL]; |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (void)saveToPhotoAlbum:(CDVInvokedUrlCommand*)command { |
||||
|
self.command = command; |
||||
|
NSArray *filenames = [command.arguments objectAtIndex:0]; |
||||
|
[self.commandDelegate runInBackground:^{ |
||||
|
bool shared = false; |
||||
|
for (NSString* filename in filenames) { |
||||
|
UIImage* image = [self getImage:filename]; |
||||
|
if (image != nil) { |
||||
|
shared = true; |
||||
|
UIImageWriteToSavedPhotosAlbum(image, self, @selector(thisImage:wasSavedToPhotoAlbumWithError:contextInfo:), nil); |
||||
|
} |
||||
|
} |
||||
|
if (!shared) { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no valid image was passed"]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId]; |
||||
|
} |
||||
|
}]; |
||||
|
} |
||||
|
|
||||
|
// called from saveToPhotoAlbum, note that we only send feedback for the first image that's being saved (not keeping the callback) |
||||
|
// but since the UIImageWriteToSavedPhotosAlbum function is only called with valid images that should not be a problem |
||||
|
- (void)thisImage:(UIImage *)image wasSavedToPhotoAlbumWithError:(NSError *)error contextInfo:(void*)ctxInfo { |
||||
|
if (error) { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.localizedDescription]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId]; |
||||
|
} else { |
||||
|
CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
||||
|
[self.commandDelegate sendPluginResult:pluginResult callbackId:self.command.callbackId]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
-(UIImage*)getImage: (NSString *)imageName { |
||||
|
UIImage *image = nil; |
||||
|
if (imageName != (id)[NSNull null]) { |
||||
|
if ([imageName hasPrefix:@"http"]) { |
||||
|
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageName]]]; |
||||
|
} else if ([imageName hasPrefix:@"www/"]) { |
||||
|
image = [UIImage imageNamed:imageName]; |
||||
|
} else if ([imageName hasPrefix:@"file://"]) { |
||||
|
image = [UIImage imageWithData:[NSData dataWithContentsOfFile:[[NSURL URLWithString:imageName] path]]]; |
||||
|
} else if ([imageName hasPrefix:@"data:"]) { |
||||
|
// using a base64 encoded string |
||||
|
NSURL *imageURL = [NSURL URLWithString:imageName]; |
||||
|
NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; |
||||
|
image = [UIImage imageWithData:imageData]; |
||||
|
} else if ([imageName hasPrefix:@"assets-library://"]) { |
||||
|
// use assets-library |
||||
|
NSURL *imageURL = [NSURL URLWithString:imageName]; |
||||
|
NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; |
||||
|
image = [UIImage imageWithData:imageData]; |
||||
|
} else { |
||||
|
// assume anywhere else, on the local filesystem |
||||
|
image = [UIImage imageWithData:[NSData dataWithContentsOfFile:imageName]]; |
||||
|
} |
||||
|
} |
||||
|
return image; |
||||
|
} |
||||
|
|
||||
|
-(NSURL*)getFile: (NSString *)fileName { |
||||
|
NSURL *file = nil; |
||||
|
if (fileName != (id)[NSNull null]) { |
||||
|
NSRange rangeData = [fileName rangeOfString:@"data:"]; |
||||
|
if ([fileName hasPrefix:@"http"]) { |
||||
|
NSURL *url = [NSURL URLWithString:fileName]; |
||||
|
NSData *fileData = [NSData dataWithContentsOfURL:url]; |
||||
|
NSString *name = (NSString*)[[fileName componentsSeparatedByString: @"/"] lastObject]; |
||||
|
file = [NSURL fileURLWithPath:[self storeInFile:[name componentsSeparatedByString: @"?"][0] fileData:fileData]]; |
||||
|
} else if ([fileName hasPrefix:@"www/"]) { |
||||
|
NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; |
||||
|
NSString *fullPath = [NSString stringWithFormat:@"%@/%@", bundlePath, fileName]; |
||||
|
file = [NSURL fileURLWithPath:fullPath]; |
||||
|
} else if ([fileName hasPrefix:@"file://"]) { |
||||
|
// stripping the first 6 chars, because the path should start with / instead of file:// |
||||
|
file = [NSURL fileURLWithPath:[fileName substringFromIndex:6]]; |
||||
|
} else if (rangeData.location != NSNotFound ){ |
||||
|
//If found "data:" |
||||
|
NSString *fileType = (NSString*)[[[fileName substringFromIndex:rangeData.location+rangeData.length] componentsSeparatedByString: @";"] objectAtIndex:0]; |
||||
|
|
||||
|
NSString* attachmentName; |
||||
|
//Find df anywhere in string |
||||
|
NSRange rangeDF = [fileName rangeOfString:@"df:"]; |
||||
|
//If not found fallback to default name |
||||
|
if (rangeDF.location == NSNotFound) { |
||||
|
attachmentName = @"attachment."; |
||||
|
attachmentName = [attachmentName stringByAppendingString:(NSString*)[[fileType componentsSeparatedByString: @"/"] lastObject]]; |
||||
|
} else { |
||||
|
//Found, apply name |
||||
|
attachmentName = (NSString*)[[[fileName substringFromIndex:rangeDF.location+rangeDF.length] componentsSeparatedByString: @";"] objectAtIndex:0]; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
NSString *base64content = (NSString*)[[fileName componentsSeparatedByString: @","] lastObject]; |
||||
|
NSData* data = [SocialSharing dataFromBase64String:base64content]; |
||||
|
file = [NSURL fileURLWithPath:[self storeInFile:attachmentName fileData:data]]; |
||||
|
|
||||
|
} else { |
||||
|
// assume anywhere else, on the local filesystem |
||||
|
file = [NSURL fileURLWithPath:fileName]; |
||||
|
} |
||||
|
} |
||||
|
return file; |
||||
|
} |
||||
|
|
||||
|
-(NSString*) storeInFile: (NSString*) fileName |
||||
|
fileData: (NSData*) fileData { |
||||
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); |
||||
|
NSString *documentsDirectory = [paths objectAtIndex:0]; |
||||
|
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName]; |
||||
|
[fileData writeToFile:filePath atomically:YES]; |
||||
|
_tempStoredFile = filePath; |
||||
|
return filePath; |
||||
|
} |
||||
|
|
||||
|
- (void) cleanupStoredFiles { |
||||
|
if (_tempStoredFile != nil) { |
||||
|
NSError *error; |
||||
|
[[NSFileManager defaultManager]removeItemAtPath:_tempStoredFile error:&error]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
+ (NSData*) dataFromBase64String:(NSString*)aString { |
||||
|
return [[NSData alloc] initWithBase64EncodedString:aString options:0]; |
||||
|
} |
||||
|
|
||||
|
#pragma mark - UIPopoverControllerDelegate methods |
||||
|
|
||||
|
- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView **)view { |
||||
|
NSArray *comps = [[self getIPadPopupCoordinates] componentsSeparatedByString:@","]; |
||||
|
CGRect newRect = [self getPopupRectFromIPadPopupCoordinates:comps]; |
||||
|
rect->origin = newRect.origin; |
||||
|
} |
||||
|
|
||||
|
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController { |
||||
|
_popover = nil; |
||||
|
} |
||||
|
|
||||
|
#pragma mark - UIDocumentInteractionControllerDelegate methods |
||||
|
|
||||
|
- (void) documentInteractionController: (UIDocumentInteractionController *) controller willBeginSendingToApplication: (NSString *) application { |
||||
|
// note that the application actually contains the app bundle id which was picked (for whatsapp and instagram only) |
||||
|
NSLog(@"SocialSharing app selected: %@", application); |
||||
|
} |
||||
|
|
||||
|
- (void) documentInteractionControllerDidDismissOpenInMenu: (UIDocumentInteractionController *) controller { |
||||
|
if (self.command != nil) { |
||||
|
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; |
||||
|
[self.commandDelegate sendPluginResult:result callbackId: self.command.callbackId]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@end |
@ -0,0 +1,177 @@ |
|||||
|
var cordova = require('cordova'); |
||||
|
|
||||
|
module.exports = { |
||||
|
share: function (win, fail, args) { |
||||
|
//Text Message
|
||||
|
var message = args[0]; |
||||
|
//Title
|
||||
|
var subject = args[1]; |
||||
|
//File(s) Path
|
||||
|
var fileOrFileArray = args[2]; |
||||
|
//Web link
|
||||
|
var url = args[3]; |
||||
|
|
||||
|
var folder = Windows.Storage.ApplicationData.current.temporaryFolder; |
||||
|
|
||||
|
var getExtension = function (strBase64) { |
||||
|
return strBase64.substring(strBase64.indexOf("/") + 1, strBase64.indexOf(";base64")); |
||||
|
}; |
||||
|
|
||||
|
var replaceAll = function (str, find, replace) { |
||||
|
return str.replace(new RegExp(find, 'g'), replace); |
||||
|
}; |
||||
|
|
||||
|
var sanitizeFilename = function (name) { |
||||
|
return replaceAll(name, "[:\\\\/*?|<> ]", "_"); |
||||
|
}; |
||||
|
|
||||
|
var getFileName = function (position, fileExtension) { |
||||
|
var fileName = (subject ? sanitizeFilename(subject) : "file") + (position == 0 ? "" : "_" + position) + "." + fileExtension; |
||||
|
return fileName; |
||||
|
}; |
||||
|
|
||||
|
var createTemporalFile = function (fileName, buffer) { |
||||
|
|
||||
|
var filePath = ""; |
||||
|
return folder.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.replaceExisting).then(function (file) { |
||||
|
filePath = file.path; |
||||
|
return Windows.Storage.FileIO.writeBufferAsync(file, buffer); |
||||
|
}).then(function(){ |
||||
|
return Windows.Storage.StorageFile.getFileFromPathAsync(filePath); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
var doShare = function (e) { |
||||
|
e.request.data.properties.title = subject?subject: "Sharing"; |
||||
|
if (message) e.request.data.setText(message); |
||||
|
if (url) e.request.data.setWebLink(new Windows.Foundation.Uri(url)); |
||||
|
if (fileOrFileArray.length > 0) { |
||||
|
var deferral = e.request.getDeferral(); |
||||
|
var storageItems = []; |
||||
|
var filesCount = fileOrFileArray.length; |
||||
|
|
||||
|
var completeFile = function () { |
||||
|
if (!--filesCount) { |
||||
|
storageItems.length && e.request.data.setStorageItems(storageItems); |
||||
|
deferral.complete(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
for (var i = 0; i < fileOrFileArray.length; i++) { |
||||
|
|
||||
|
var file = fileOrFileArray[i]; |
||||
|
if (file.indexOf("data:") >= 0) { |
||||
|
var fileName = getFileName(i, getExtension(file)); |
||||
|
var buffer = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(file.split(',')[1]); |
||||
|
if (buffer) { |
||||
|
createTemporalFile(fileName, buffer).done( |
||||
|
function (file) { |
||||
|
storageItems.push(file); |
||||
|
completeFile(); |
||||
|
}, |
||||
|
function () { |
||||
|
completeFile(); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
else { |
||||
|
completeFile(); |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
Windows.Storage.StorageFile.getFileFromPathAsync(file).done( |
||||
|
function (file) { |
||||
|
storageItems.push(file); |
||||
|
completeFile(); |
||||
|
}, |
||||
|
function () { |
||||
|
completeFile(); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); |
||||
|
|
||||
|
dataTransferManager.addEventListener("datarequested", doShare); |
||||
|
|
||||
|
try { |
||||
|
Windows.ApplicationModel.DataTransfer.DataTransferManager.showShareUI(); |
||||
|
win(true); |
||||
|
} catch (err) { |
||||
|
fail(err); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
canShareViaEmail: function (win, fail, args) { |
||||
|
win(true); |
||||
|
}, |
||||
|
|
||||
|
shareViaEmail: function (win, fail, args) { |
||||
|
//Text Message
|
||||
|
var message = args[0]; |
||||
|
//Title
|
||||
|
var subject = args[1]; |
||||
|
//File(s) Path
|
||||
|
var fileOrFileArray = args[5]; |
||||
|
|
||||
|
var doShare = function (e) { |
||||
|
e.request.data.properties.title = subject ? subject : "Sharing"; |
||||
|
if (message) { |
||||
|
var htmlFormat = Windows.ApplicationModel.DataTransfer.HtmlFormatHelper.createHtmlFormat(message); |
||||
|
e.request.data.setHtmlFormat(htmlFormat); |
||||
|
} |
||||
|
|
||||
|
if (fileOrFileArray.length > 0) { |
||||
|
var deferral = e.request.getDeferral(); |
||||
|
var storageItems = []; |
||||
|
var filesCount = fileOrFileArray.length; |
||||
|
for (var i = 0; i < fileOrFileArray.length; i++) { |
||||
|
Windows.Storage.StorageFile.getFileFromPathAsync(fileOrFileArray[i]).done( |
||||
|
function (index, file) { |
||||
|
var path = fileOrFileArray[index]; |
||||
|
var streamRef = Windows.Storage.Streams.RandomAccessStreamReference.createFromFile(file); |
||||
|
e.request.data.resourceMap[path] = streamRef; |
||||
|
storageItems.push(file); |
||||
|
if (!--filesCount) { |
||||
|
e.request.data.setStorageItems(storageItems); |
||||
|
deferral.complete(); |
||||
|
} |
||||
|
}.bind(this, i), |
||||
|
function () { |
||||
|
if (!--filesCount) { |
||||
|
e.request.data.setStorageItems(storageItems); |
||||
|
deferral.complete(); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); |
||||
|
|
||||
|
dataTransferManager.addEventListener("datarequested", doShare); |
||||
|
|
||||
|
try { |
||||
|
Windows.ApplicationModel.DataTransfer.DataTransferManager.showShareUI(); |
||||
|
win(true); |
||||
|
} catch (err) { |
||||
|
fail(err); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
shareViaSMS: function (win, fail, args) { |
||||
|
var chatMessage = new Windows.ApplicationModel.Chat.ChatMessage(); |
||||
|
chatMessage.body = args[0].message; |
||||
|
if (!!args[1]) { |
||||
|
chatMessage.recipients.push(args[1]); |
||||
|
} |
||||
|
Windows.ApplicationModel.Chat.ChatMessageManager.showComposeSmsMessageAsync(chatMessage).done(win, fail); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
require("cordova/exec/proxy").add("SocialSharing", module.exports); |
@ -0,0 +1,103 @@ |
|||||
|
using Microsoft.Phone.Tasks; |
||||
|
|
||||
|
using WPCordovaClassLib.Cordova; |
||||
|
using WPCordovaClassLib.Cordova.Commands; |
||||
|
using WPCordovaClassLib.Cordova.JSON; |
||||
|
|
||||
|
using Newtonsoft.Json; |
||||
|
|
||||
|
namespace Cordova.Extension.Commands |
||||
|
{ |
||||
|
public class SocialSharing : BaseCommand |
||||
|
{ |
||||
|
|
||||
|
public void available(string jsonArgs) |
||||
|
{ |
||||
|
DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); |
||||
|
} |
||||
|
|
||||
|
public void share(string jsonArgs) |
||||
|
{ |
||||
|
|
||||
|
var options = JsonHelper.Deserialize<string[]>(jsonArgs); |
||||
|
|
||||
|
var message = options[0]; |
||||
|
var title = options[1]; |
||||
|
var files = JsonHelper.Deserialize<string[]>(options[2]); |
||||
|
var link = options[3]; |
||||
|
|
||||
|
if (link != null && !"null".Equals(link)) |
||||
|
{ |
||||
|
ShareLinkTask shareLinkTask = new ShareLinkTask(); |
||||
|
shareLinkTask.Title = title; |
||||
|
shareLinkTask.LinkUri = new System.Uri(link, System.UriKind.Absolute); |
||||
|
shareLinkTask.Message = message; |
||||
|
shareLinkTask.Show(); |
||||
|
} |
||||
|
else if (files != null && files.Length > 0) |
||||
|
{ |
||||
|
ShareLinkTask shareLinkTask = new ShareLinkTask(); |
||||
|
shareLinkTask.Title = title; |
||||
|
shareLinkTask.LinkUri = new System.Uri(files[0], System.UriKind.Absolute); |
||||
|
shareLinkTask.Message = message; |
||||
|
shareLinkTask.Show(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var shareStatusTask = new ShareStatusTask { Status = message }; |
||||
|
shareStatusTask.Show(); |
||||
|
} |
||||
|
// unfortunately, there is no way to tell if something was shared, so just invoke the successCallback
|
||||
|
DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); |
||||
|
} |
||||
|
|
||||
|
public void canShareViaEmail(string jsonArgs) |
||||
|
{ |
||||
|
DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); |
||||
|
} |
||||
|
|
||||
|
// HTML and attachments are currently not supported on WP8
|
||||
|
public void shareViaEmail(string jsonArgs) |
||||
|
{ |
||||
|
var options = JsonHelper.Deserialize<string[]>(jsonArgs); |
||||
|
EmailComposeTask draft = new EmailComposeTask(); |
||||
|
draft.Body = options[0]; |
||||
|
draft.Subject = options[1]; |
||||
|
if (!"null".Equals(options[2])) |
||||
|
{ |
||||
|
draft.To = string.Join(",", options[2]); |
||||
|
} |
||||
|
if (!"null".Equals(options[3])) |
||||
|
{ |
||||
|
draft.Cc = string.Join(",", options[3]); |
||||
|
} |
||||
|
if (!"null".Equals(options[4])) |
||||
|
{ |
||||
|
draft.Bcc = string.Join(",", options[4]); |
||||
|
} |
||||
|
draft.Show(); |
||||
|
DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true)); |
||||
|
} |
||||
|
|
||||
|
public void shareViaSMS(string jsonArgs) |
||||
|
{ |
||||
|
var options = JsonHelper.Deserialize<string[]>(jsonArgs); |
||||
|
|
||||
|
SmsComposeTask smsComposeTask = new SmsComposeTask(); |
||||
|
|
||||
|
smsComposeTask.To = options[1]; |
||||
|
SMSMessageClass m = JsonConvert.DeserializeObject<SMSMessageClass>(options[0]); |
||||
|
smsComposeTask.Body = m.message; |
||||
|
|
||||
|
smsComposeTask.Show(); |
||||
|
|
||||
|
DispatchCommandResult(new PluginResult(PluginResult.Status.OK, true)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class SMSMessageClass |
||||
|
{ |
||||
|
public string message { get; set; } |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
|
||||
|
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" |
||||
|
xmlns:rim="http://www.blackberry.com/ns/widgets" |
||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
id="nl.x-services.plugins.socialsharing.tests" |
||||
|
version="4.3.15"> |
||||
|
<name>SocialSharing Tests</name> |
||||
|
<author>Nicolas Oliver</author> |
||||
|
<license>MIT</license> |
||||
|
|
||||
|
<js-module src="test.js" name="tests"></js-module> |
||||
|
</plugin> |
@ -0,0 +1,363 @@ |
|||||
|
/** |
||||
|
* Jasmine Based test suites |
||||
|
* |
||||
|
* Several of SocialSharing APIs cannot be automatically tested, because |
||||
|
* they depend on user interaction in order to call success or fail |
||||
|
* handlers. For most of them, there is a basic test that assert the presence of |
||||
|
* the API. |
||||
|
* |
||||
|
* There are some cases that test automation can be applied, i.e in "canShareVia", |
||||
|
* "canShareViaEmail" and "available" methods. For those cases, there is some level |
||||
|
* of automatic test coverage. |
||||
|
*/ |
||||
|
exports.defineAutoTests = function () { |
||||
|
'use strict'; |
||||
|
|
||||
|
describe('socialsharing', function () { |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
describe('share', function () { |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing.share).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
it('should be a function', function () { |
||||
|
expect(typeof window.plugins.socialsharing.share).toEqual('function'); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('shareVia', function () { |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing.shareVia).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
it('should be a function', function () { |
||||
|
expect(typeof window.plugins.socialsharing.shareVia).toEqual('function'); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('shareViaTwitter', function () { |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing.shareViaTwitter).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
it('should be a function', function () { |
||||
|
expect(typeof window.plugins.socialsharing.shareViaTwitter).toEqual('function'); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('shareViaFacebook', function () { |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing.shareViaFacebook).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
it('should be a function', function () { |
||||
|
expect(typeof window.plugins.socialsharing.shareViaFacebook).toEqual('function'); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('shareViaFacebookWithPasteMessageHint', function () { |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing.shareViaFacebookWithPasteMessageHint).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
it('should be a function', function () { |
||||
|
expect(typeof window.plugins.socialsharing.shareViaFacebookWithPasteMessageHint).toEqual('function'); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('shareViaWhatsApp', function () { |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing.shareViaWhatsApp).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
it('should be a function', function () { |
||||
|
expect(typeof window.plugins.socialsharing.shareViaWhatsApp).toEqual('function'); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('shareViaSMS', function () { |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing.shareViaSMS).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
it('should be a function', function () { |
||||
|
expect(typeof window.plugins.socialsharing.shareViaSMS).toEqual('function'); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('shareViaEmail', function () { |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing.shareViaEmail).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
it('should be a function', function () { |
||||
|
expect(typeof window.plugins.socialsharing.shareViaEmail).toEqual('function'); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('canShareVia', function () { |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing.canShareVia).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
it('should be a function', function () { |
||||
|
expect(typeof window.plugins.socialsharing.canShareVia).toEqual('function'); |
||||
|
}); |
||||
|
|
||||
|
it('should always call callback or error function', function (done) { |
||||
|
function onSuccess(data){ |
||||
|
expect(data).not.toEqual(null); |
||||
|
done(); |
||||
|
} |
||||
|
|
||||
|
function onError(error){ |
||||
|
expect(error).not.toEqual(null); |
||||
|
done(); |
||||
|
} |
||||
|
|
||||
|
window.plugins.socialsharing.canShareVia('dummytarget','dummymessage', null, null, null, onSuccess, onError); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('canshareViaEmail', function(){ |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing.canShareViaEmail).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
it('should be a function', function () { |
||||
|
expect(typeof window.plugins.socialsharing.canShareViaEmail).toEqual('function'); |
||||
|
}); |
||||
|
|
||||
|
it('should always call callback or error function', function (done) { |
||||
|
function onSuccess(data){ |
||||
|
expect(data).not.toEqual(null); |
||||
|
done(); |
||||
|
} |
||||
|
|
||||
|
function onError(error){ |
||||
|
expect(error).not.toEqual(null); |
||||
|
done(); |
||||
|
} |
||||
|
|
||||
|
window.plugins.socialsharing.canShareViaEmail(onSuccess, onError); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
describe('availabe', function(){ |
||||
|
it('should be defined', function () { |
||||
|
expect(window.plugins.socialsharing.available).toBeDefined(); |
||||
|
}); |
||||
|
|
||||
|
it('should be a function', function () { |
||||
|
expect(typeof window.plugins.socialsharing.available).toEqual('function'); |
||||
|
}); |
||||
|
|
||||
|
it('should return a boolean when called', function(done){ |
||||
|
window.plugins.socialsharing.available(function(isAvailable) { |
||||
|
expect(typeof isAvailable).toEqual('boolean'); |
||||
|
done(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Manual tests suites |
||||
|
* |
||||
|
* Some actions buttons to execute SocialSharing plugin methods |
||||
|
*/ |
||||
|
exports.defineManualTests = function (contentEl, createActionButton) { |
||||
|
'use strict'; |
||||
|
|
||||
|
/** helper function to log messages in the log div element */ |
||||
|
function logMessage(message, color) { |
||||
|
var log = document.getElementById('info'), |
||||
|
logLine = document.createElement('div'); |
||||
|
|
||||
|
if (color) { |
||||
|
logLine.style.color = color; |
||||
|
} |
||||
|
|
||||
|
logLine.innerHTML = message; |
||||
|
log.appendChild(logLine); |
||||
|
} |
||||
|
|
||||
|
/** helper function to clear the log div element */ |
||||
|
function clearLog() { |
||||
|
var log = document.getElementById('info'); |
||||
|
log.innerHTML = ''; |
||||
|
} |
||||
|
|
||||
|
/** helper function to declare a not implemented test */ |
||||
|
function testNotImplemented(testName) { |
||||
|
return function () { |
||||
|
console.error(testName, 'test not implemented'); |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** init method called on deviceready event */ |
||||
|
function init() {} |
||||
|
|
||||
|
/** object to hold properties and configs */ |
||||
|
var TestSuite = {}; |
||||
|
|
||||
|
TestSuite.getCanShareViaTarget = function(){ |
||||
|
return document.getElementById('inputCanShareVia').value; |
||||
|
}; |
||||
|
|
||||
|
TestSuite.$markup = '' + |
||||
|
'<fieldset>' + |
||||
|
'<legend>Available Tests</legend>' + |
||||
|
|
||||
|
'<h3>Available</h3>' + |
||||
|
'<div id="buttonIsAvailable"></div>' + |
||||
|
'Expected result: Should log if the plugin is available or not' + |
||||
|
'</fieldset>' + |
||||
|
|
||||
|
'<fieldset>' + |
||||
|
'<legend>Share Tests</legend>' + |
||||
|
|
||||
|
'<h3>Share Message</h3>' + |
||||
|
'<div id="buttonShareMessage"></div>' + |
||||
|
'Expected result: Should display share widget, and the message to share should contain "Message body"' + |
||||
|
|
||||
|
'<h3>Share Message with Subject</h3>' + |
||||
|
'<div id="buttonShareMessageWithSubject"></div>' + |
||||
|
'Expected result: Should display share widget, and the message to share should contain "Message body", and the subject should be "Message subject"' + |
||||
|
|
||||
|
'<h3>Share Link</h3>' + |
||||
|
'<div id="buttonShareLink"></div>' + |
||||
|
'Expected result: Should display share widget, and the message to share should contain "http://www.x-services.nl"' + |
||||
|
|
||||
|
'<h3>Share Message with Link</h3>' + |
||||
|
'<div id="buttonShareMessageAndLink"></div>' + |
||||
|
'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl"' + |
||||
|
|
||||
|
'<h3>Share Image</h3>' + |
||||
|
'<div id="buttonShareImage"></div>' + |
||||
|
'Expected result: Should display share widget, and the message to share should contain an image' + |
||||
|
|
||||
|
'<h3>Share Image in base 64</h3>' + |
||||
|
'<div id="buttonShareImageBase64"></div>' + |
||||
|
'Expected result: Should display share widget, and the message to share should contain an image. The image is encoded in base 64' + |
||||
|
|
||||
|
'<h3>Share Image with Message</h3>' + |
||||
|
'<div id="buttonShareMessageImage"></div>' + |
||||
|
'Expected result: Should display share widget, and the message to share should contain "Message body" and an image' + |
||||
|
|
||||
|
'<h3>Share Image with Message and Link</h3>' + |
||||
|
'<div id="buttonShareMessageImageLink"></div>' + |
||||
|
'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl" and an image' + |
||||
|
|
||||
|
'<h3>Share Image with Message, Subject and Link</h3>' + |
||||
|
'<div id="buttonShareMessageSubjectImageLink"></div>' + |
||||
|
'Expected result: Should display share widget, and the message to share should contain "Message body http://www.x-services.nl", "Message subject" as subject, and an image' + |
||||
|
'</fieldset>' + |
||||
|
|
||||
|
'<fieldset>' + |
||||
|
'<legend>Can Share Tests</legend>' + |
||||
|
|
||||
|
'Target: <input id="inputCanShareVia" type="text"/><br>' + |
||||
|
|
||||
|
'<h3>Can Share via</h3>' + |
||||
|
'<div id="buttonCanShareVia"></div>' + |
||||
|
'Expected result: should log OK if can share, or should log a list of available share targets' + |
||||
|
|
||||
|
'<h3>Can Share via Email</h3>' + |
||||
|
'<div id="buttonCanShareViaEmail"></div>' + |
||||
|
'Expected result: should log OK if can share' + |
||||
|
'</fieldset>' + |
||||
|
''; |
||||
|
|
||||
|
contentEl.innerHTML = '<div id="info"></div>' + TestSuite.$markup; |
||||
|
|
||||
|
createActionButton('availabe', function () { |
||||
|
clearLog(); |
||||
|
window.plugins.socialsharing.available(function(isAvailable) { |
||||
|
var message = 'is this plugin available? '; |
||||
|
message += isAvailable? 'Yes' : 'No'; |
||||
|
|
||||
|
logMessage(message, isAvailable? 'green' : 'red'); |
||||
|
}); |
||||
|
}, 'buttonIsAvailable'); |
||||
|
|
||||
|
createActionButton('share message', function () { |
||||
|
window.plugins.socialsharing.share('Message body'); |
||||
|
}, 'buttonShareMessage'); |
||||
|
|
||||
|
createActionButton('share message and subject', function () { |
||||
|
window.plugins.socialsharing.share('Message body', 'Message subject'); |
||||
|
}, 'buttonShareMessageWithSubject'); |
||||
|
|
||||
|
createActionButton('share link', function () { |
||||
|
window.plugins.socialsharing.share(null, null, null, 'http://www.x-services.nl'); |
||||
|
}, 'buttonShareLink'); |
||||
|
|
||||
|
createActionButton('share message and link', function () { |
||||
|
window.plugins.socialsharing.share('Message body', null, null, 'http://www.x-services.nl'); |
||||
|
}, 'buttonShareMessageAndLink'); |
||||
|
|
||||
|
createActionButton('share image', function () { |
||||
|
window.plugins.socialsharing.share(null, null, 'https://www.google.nl/images/srpr/logo4w.png', null); |
||||
|
}, 'buttonShareImage'); |
||||
|
|
||||
|
createActionButton('share image base 64', function () { |
||||
|
window.plugins.socialsharing.share(null, 'Android filename', 'data:image/png;base64,R0lGODlhDAAMALMBAP8AAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAUKAAEALAAAAAAMAAwAQAQZMMhJK7iY4p3nlZ8XgmNlnibXdVqolmhcRQA7', null); |
||||
|
}, 'buttonShareImageBase64'); |
||||
|
|
||||
|
createActionButton('share message with image', function () { |
||||
|
window.plugins.socialsharing.share('Message body', null, 'https://www.google.nl/images/srpr/logo4w.png', null); |
||||
|
}, 'buttonShareMessageImage'); |
||||
|
|
||||
|
createActionButton('share message, image, and link', function () { |
||||
|
window.plugins.socialsharing.share('Message body', null, 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl'); |
||||
|
}, 'buttonShareMessageImageLink'); |
||||
|
|
||||
|
createActionButton('share message, subject, image, and link', function () { |
||||
|
window.plugins.socialsharing.share('Message body', 'Message subject', 'https://www.google.nl/images/srpr/logo4w.png', 'http://www.x-services.nl'); |
||||
|
}, 'buttonShareMessageSubjectImageLink'); |
||||
|
|
||||
|
createActionButton('can share via', function () { |
||||
|
var target = TestSuite.getCanShareViaTarget(); |
||||
|
|
||||
|
if(!target){ |
||||
|
console.error('must have a canShareVia target'); |
||||
|
} |
||||
|
else { |
||||
|
clearLog(); |
||||
|
window.plugins.socialsharing.canShareVia(target, 'msg', null, null, null, function(e){ |
||||
|
console.log('canShareVia success, see log for more information'); |
||||
|
logMessage('canShareVia: ' + e,'green'); |
||||
|
}, function(e){ |
||||
|
console.error('canShareVia fail, see log for more information'); |
||||
|
var message = "Share targets<br>"; |
||||
|
|
||||
|
message += "<ul>"; |
||||
|
|
||||
|
e.forEach(function(target){ |
||||
|
message += "<li>" + target + "</li>"; |
||||
|
}); |
||||
|
|
||||
|
message += "</ul>"; |
||||
|
logMessage(message,'red'); |
||||
|
}); |
||||
|
} |
||||
|
}, 'buttonCanShareVia'); |
||||
|
|
||||
|
createActionButton('can share via email', function () { |
||||
|
clearLog(); |
||||
|
window.plugins.socialsharing.canShareViaEmail(function(e){ |
||||
|
console.log('canShareViaEmail success, see log for more information'); |
||||
|
logMessage('canShareViaEmail: ' + e,'green'); |
||||
|
}, function(e){ |
||||
|
console.error('canShareViaEmail fail, see log for more information'); |
||||
|
logMessage('canShareViaEmail: ' + e,'red'); |
||||
|
}); |
||||
|
}, 'buttonCanShareViaEmail'); |
||||
|
|
||||
|
document.addEventListener('deviceready', init, false); |
||||
|
}; |
@ -0,0 +1,122 @@ |
|||||
|
function SocialSharing() { |
||||
|
} |
||||
|
|
||||
|
// Override this method (after deviceready) to set the location where you want the iPad popup arrow to appear.
|
||||
|
// If not overridden with different values, the popup is not used. Example:
|
||||
|
//
|
||||
|
// window.plugins.socialsharing.iPadPopupCoordinates = function() {
|
||||
|
// return "100,100,200,300";
|
||||
|
// };
|
||||
|
SocialSharing.prototype.iPadPopupCoordinates = function () { |
||||
|
// left,top,width,height
|
||||
|
return "-1,-1,-1,-1"; |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.setIPadPopupCoordinates = function (coords) { |
||||
|
// left,top,width,height
|
||||
|
cordova.exec(function() {}, this._getErrorCallback(function() {}, "setIPadPopupCoordinates"), "SocialSharing", "setIPadPopupCoordinates", [coords]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.available = function (callback) { |
||||
|
cordova.exec(function (avail) { |
||||
|
callback(avail ? true : false); |
||||
|
}, null, "SocialSharing", "available", []); |
||||
|
}; |
||||
|
|
||||
|
// this is the recommended way to share as it is the most feature-rich with respect to what you pass in and get back
|
||||
|
SocialSharing.prototype.shareWithOptions = function (options, successCallback, errorCallback) { |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareWithOptions"), "SocialSharing", "shareWithOptions", [options]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.share = function (message, subject, fileOrFileArray, url, successCallback, errorCallback) { |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "share"), "SocialSharing", "share", [message, subject, this._asArray(fileOrFileArray), url]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.shareViaTwitter = function (message, file /* multiple not allowed by twitter */, url, successCallback, errorCallback) { |
||||
|
var fileArray = this._asArray(file); |
||||
|
var ecb = this._getErrorCallback(errorCallback, "shareViaTwitter"); |
||||
|
if (fileArray.length > 1) { |
||||
|
ecb("shareViaTwitter supports max one file"); |
||||
|
} else { |
||||
|
cordova.exec(successCallback, ecb, "SocialSharing", "shareViaTwitter", [message, null, fileArray, url]); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.shareViaFacebook = function (message, fileOrFileArray, url, successCallback, errorCallback) { |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaFacebook"), "SocialSharing", "shareViaFacebook", [message, null, this._asArray(fileOrFileArray), url]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.shareViaFacebookWithPasteMessageHint = function (message, fileOrFileArray, url, pasteMessageHint, successCallback, errorCallback) { |
||||
|
pasteMessageHint = pasteMessageHint || "If you like you can paste a message from your clipboard"; |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaFacebookWithPasteMessageHint"), "SocialSharing", "shareViaFacebookWithPasteMessageHint", [message, null, this._asArray(fileOrFileArray), url, pasteMessageHint]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.shareViaWhatsApp = function (message, fileOrFileArray, url, successCallback, errorCallback) { |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaWhatsApp"), "SocialSharing", "shareViaWhatsApp", [message, null, this._asArray(fileOrFileArray), url, null]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.shareViaWhatsAppToReceiver = function (receiver, message, fileOrFileArray, url, successCallback, errorCallback) { |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaWhatsAppToReceiver"), "SocialSharing", "shareViaWhatsApp", [message, null, this._asArray(fileOrFileArray), url, receiver]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.shareViaSMS = function (options, phonenumbers, successCallback, errorCallback) { |
||||
|
var opts = options; |
||||
|
if (typeof options == "string") { |
||||
|
opts = {"message":options}; // for backward compatibility as the options param used to be the message
|
||||
|
} |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaSMS"), "SocialSharing", "shareViaSMS", [opts, phonenumbers]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.shareViaEmail = function (message, subject, toArray, ccArray, bccArray, fileOrFileArray, successCallback, errorCallback) { |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaEmail"), "SocialSharing", "shareViaEmail", [message, subject, this._asArray(toArray), this._asArray(ccArray), this._asArray(bccArray), this._asArray(fileOrFileArray)]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.canShareVia = function (via, message, subject, fileOrFileArray, url, successCallback, errorCallback) { |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "canShareVia"), "SocialSharing", "canShareVia", [message, subject, this._asArray(fileOrFileArray), url, via]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.canShareViaEmail = function (successCallback, errorCallback) { |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "canShareViaEmail"), "SocialSharing", "canShareViaEmail", []); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.shareViaInstagram = function (message, fileOrFileArray, successCallback, errorCallback) { |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareViaInstagram"), "SocialSharing", "shareViaInstagram", [message, null, this._asArray(fileOrFileArray), null]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.shareVia = function (via, message, subject, fileOrFileArray, url, successCallback, errorCallback) { |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "shareVia"), "SocialSharing", "shareVia", [message, subject, this._asArray(fileOrFileArray), url, via]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype.saveToPhotoAlbum = function (fileOrFileArray, successCallback, errorCallback) { |
||||
|
cordova.exec(successCallback, this._getErrorCallback(errorCallback, "saveToPhotoAlbum"), "SocialSharing", "saveToPhotoAlbum", [this._asArray(fileOrFileArray)]); |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype._asArray = function (param) { |
||||
|
if (param == null) { |
||||
|
param = []; |
||||
|
} else if (typeof param === 'string') { |
||||
|
param = new Array(param); |
||||
|
} |
||||
|
return param; |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.prototype._getErrorCallback = function (ecb, functionName) { |
||||
|
if (typeof ecb === 'function') { |
||||
|
return ecb; |
||||
|
} else { |
||||
|
return function (result) { |
||||
|
console.log("The injected error callback of '" + functionName + "' received: " + JSON.stringify(result)); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
SocialSharing.install = function () { |
||||
|
if (!window.plugins) { |
||||
|
window.plugins = {}; |
||||
|
} |
||||
|
|
||||
|
window.plugins.socialsharing = new SocialSharing(); |
||||
|
return window.plugins.socialsharing; |
||||
|
}; |
||||
|
|
||||
|
cordova.addConstructor(SocialSharing.install); |
@ -1,24 +1,26 @@ |
|||||
<ion-view view-title="Event"> |
<ion-view view-title="Event"> |
||||
<ion-content> |
<ion-content> |
||||
<a class="item item-avatar" ng-href="#/app/users/{{event.user._id}}"> |
|
||||
<img ng-src="{{event.user.img}}"> |
|
||||
<h2>{{event.user.username}}</h2> |
|
||||
<p>{{event.user.description}}</p> |
|
||||
|
<a class="item item-avatar" ng-href="#/app/users/{{event.user._id}}"> |
||||
|
<img ng-src="{{event.user.img}}"> |
||||
|
<h2>{{event.user.username}}</h2> |
||||
|
<p>{{event.user.description}}</p> |
||||
</a> |
</a> |
||||
<a class="item"> |
|
||||
<h2>{{event.title}}</h2> |
|
||||
<p>{{event.description}}</p> |
|
||||
<p>{{event.date | date: 'HH:mm, dd/MM/yyyy'}}</p> |
|
||||
</a> |
|
||||
|
|
||||
<div class="item item-image"> |
|
||||
<img ng-src="{{event.img}}"> |
|
||||
</div> |
|
||||
|
<div class="item item-image"> |
||||
|
<img ng-src="{{event.img}}"> |
||||
|
</div> |
||||
|
<div class="item"> |
||||
|
<h2>{{event.title}}</h2> |
||||
|
<p>{{event.description}}</p> |
||||
|
<p>{{event.date | date: 'HH:mm, dd/MM/yyyy'}}</p> |
||||
|
</div> |
||||
|
|
||||
<div class="item"> |
<div class="item"> |
||||
<h2>Map</h2> |
|
||||
|
<h2>Map</h2> |
||||
</div> |
</div> |
||||
|
|
||||
<leaflet width="100%" height="40%" markers="markers" center="center" tiles="tiles" id="map-simple-map"></leaflet> |
<leaflet width="100%" height="40%" markers="markers" center="center" tiles="tiles" id="map-simple-map"></leaflet> |
||||
|
|
||||
|
<a class="item item-icon-left ct_indigo500" ng-click="share()"> |
||||
|
<i class="icon ion-android-share-alt"></i> Share |
||||
|
</a> |
||||
</ion-content> |
</ion-content> |
||||
</ion-view> |
</ion-view> |