diff --git a/Android/BarcodeScanner/README.md b/Android/BarcodeScanner/README.md index cef0b24a..585ef5c1 100644 --- a/Android/BarcodeScanner/README.md +++ b/Android/BarcodeScanner/README.md @@ -5,14 +5,17 @@ By Matt Kane This plugin requires the end user to install [the ZXing Barcode Scanner app](http://code.google.com/p/zxing/) If the user doesn't have the app install they will be promped to install it the first time the plugin is used. -1. To install the plugin, move barcodescanner.js to your project's www folder and include a reference to it +1. To install the plugin, move `barcodescanner.js` to your project's www folder and include a reference to it in your html files. 2. Create a folder called 'src/com/beetight/barcodescanner' within your project's src/com/ folder. 3. And copy the java file into that new folder. -`mkdir /src/com/beetight/barcodescanner` + mkdir -p /src/com/beetight/barcodescanner + cp ./BarcodeScanner.java /src/com/beetight/barcodescanner + +4. Add a plugin line to `res/xml/plugins.xml` -`cp ./BarcodeScanner.java /src/com/beetight/barcodescanner` + `` ## Using the plugin ## The plugin creates the object `window.plugins.barcodeScanner` with one method `scan(types, success, fail, options)` @@ -67,12 +70,12 @@ Supported encoding types: A full example could be: - window.plugins.barcodeScanner.encode(BarcodeScanner.Encode.TEXT_TYPE, "http://www.nytimes.com", function(success) { - alert("encode success: " + success); - }, function(fail) { - alert("encoding failed: " + fail); - }, {yesString: "Install"} - ); + window.plugins.barcodeScanner.encode(BarcodeScanner.Encode.TEXT_TYPE, "http://www.nytimes.com", function(success) { + alert("encode success: " + success); + }, function(fail) { + alert("encoding failed: " + fail); + }, {yesString: "Install"} + ); ## BUGS AND CONTRIBUTIONS ## diff --git a/Android/ChildBrowser/src/com/phonegap/plugins/childBrowser/ChildBrowser.java b/Android/ChildBrowser/src/com/phonegap/plugins/childBrowser/ChildBrowser.java index ad03231d..915b0bb9 100644 --- a/Android/ChildBrowser/src/com/phonegap/plugins/childBrowser/ChildBrowser.java +++ b/Android/ChildBrowser/src/com/phonegap/plugins/childBrowser/ChildBrowser.java @@ -15,6 +15,7 @@ import org.json.JSONObject; import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; @@ -26,6 +27,7 @@ import android.view.Window; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.InputMethodManager; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.EditText; @@ -46,6 +48,7 @@ public class ChildBrowser extends Plugin { private Dialog dialog; private WebView webview; + private EditText edittext; private boolean showLocationBar = true; /** @@ -64,6 +67,11 @@ public PluginResult execute(String action, JSONArray args, String callbackId) { if (action.equals("showWebPage")) { this.browserCallbackId = callbackId; + // If the ChildBrowser is already open then throw an error + if (dialog != null && dialog.isShowing()) { + return new PluginResult(PluginResult.Status.ERROR, "ChildBrowser is already open"); + } + result = this.showWebPage(args.getString(0), args.optJSONObject(1)); if (result.length() > 0) { @@ -129,7 +137,7 @@ public String openExternal(String url, boolean usePhoneGap) { this.ctx.startActivity(intent); return ""; } catch (android.content.ActivityNotFoundException e) { - System.out.println("ChildBrowser: Error loading url "+url+":"+ e.toString()); + Log.d(LOG_TAG, "ChildBrowser: Error loading url "+url+":"+ e.toString()); return e.toString(); } } @@ -166,11 +174,15 @@ private void goForward() { * * @param url to load */ - private void navigate(String url) { + private void navigate(String url) { + InputMethodManager imm = (InputMethodManager)this.ctx.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0); + if (!url.startsWith("http")) { this.webview.loadUrl("http://" + url); } this.webview.loadUrl(url); + this.webview.requestFocus(); } @@ -255,7 +267,7 @@ public void onClick(View v) { } forward.setLayoutParams(forwardParams); - final EditText edittext = new EditText(ctx); + edittext = new EditText(ctx); edittext.setOnKeyListener(new View.OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { // If the event is a key-down event on the "enter" button @@ -270,7 +282,7 @@ public boolean onKey(View v, int keyCode, KeyEvent event) { edittext.setSingleLine(true); edittext.setText(url); edittext.setLayoutParams(editParams); - + ImageButton close = new ImageButton(ctx); close.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { @@ -287,11 +299,16 @@ public void onClick(View v) { webview = new WebView(ctx); webview.getSettings().setJavaScriptEnabled(true); + webview.getSettings().setBuiltInZoomControls(true); WebViewClient client = new ChildBrowserClient(ctx, edittext); webview.setWebViewClient(client); webview.loadUrl(url); webview.setId(5); + webview.setInitialScale(0); webview.setLayoutParams(wvParams); + webview.requestFocus(); + webview.requestFocusFromTouch(); + toolbar.addView(back); toolbar.addView(forward); @@ -363,7 +380,7 @@ public ChildBrowserClient(PhonegapActivity mContext, EditText mEditText) { public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); String newloc; - if (url.startsWith("http")) { + if (url.startsWith("http:")) { newloc = url; } else { newloc = "http://" + url; diff --git a/Android/ChildBrowser/www/childbrowser.js b/Android/ChildBrowser/www/childbrowser.js index 31468470..d309f06a 100644 --- a/Android/ChildBrowser/www/childbrowser.js +++ b/Android/ChildBrowser/www/childbrowser.js @@ -27,7 +27,7 @@ ChildBrowser.prototype.showWebPage = function(url, options) { var options = new Object(); options.showLocationBar = true; } - PhoneGap.exec(this._onEvent, null, "ChildBrowser", "showWebPage", [url, options]); + PhoneGap.exec(this._onEvent, this._onError, "ChildBrowser", "showWebPage", [url, options]); }; /** @@ -45,25 +45,35 @@ ChildBrowser.prototype.close = function() { * @param usePhoneGap Load url in PhoneGap webview [optional] */ ChildBrowser.prototype.openExternal = function(url, usePhoneGap) { - PhoneGap.exec(null, null, "ChildBrowser", "openExternal", [url, usePhoneGap]); + if (usePhoneGap === true) { + navigator.app.loadUrl(url); + } + else { + PhoneGap.exec(null, null, "ChildBrowser", "openExternal", [url, usePhoneGap]); + } }; /** - * Method called when the child browser is closed. + * Method called when the child browser has an event. */ ChildBrowser.prototype._onEvent = function(data) { - console.log("In _onEvent"); - console.log("data type = " + data.type); if (data.type == ChildBrowser.CLOSE_EVENT && typeof window.plugins.childBrowser.onClose === "function") { - console.log("Calling onClose"); window.plugins.childBrowser.onClose(); } if (data.type == ChildBrowser.LOCATION_CHANGED_EVENT && typeof window.plugins.childBrowser.onLocationChange === "function") { - console.log("Calling onLocChange"); window.plugins.childBrowser.onLocationChange(data.location); } }; +/** + * Method called when the child browser has an error. + */ +ChildBrowser.prototype._onError = function(data) { + if (typeof window.plugins.childBrowser.onError === "function") { + window.plugins.childBrowser.onError(data); + } +}; + /** * Maintain API consistency with iOS */ diff --git a/Android/DateTimePicker/DatePickerPlugin.java b/Android/DateTimePicker/DatePickerPlugin.java new file mode 100644 index 00000000..a92248a7 --- /dev/null +++ b/Android/DateTimePicker/DatePickerPlugin.java @@ -0,0 +1,152 @@ +/** + * + */ +package com.ngapplication.plugin; + +import java.util.Calendar; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.DatePickerDialog; +import android.app.DatePickerDialog.OnDateSetListener; +import android.app.TimePickerDialog; +import android.app.TimePickerDialog.OnTimeSetListener; +import android.util.Log; +import android.widget.DatePicker; +import android.widget.TimePicker; + +import com.phonegap.api.PhonegapActivity; +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; +import com.phonegap.api.PluginResult.Status; + +/** + * @author ng4e + * + */ +public class DatePickerPlugin extends Plugin { + + private static final String ACTION_DATE = "date"; + private static final String ACTION_TIME = "time"; + + /* + * (non-Javadoc) + * + * @see com.phonegap.api.Plugin#execute(java.lang.String, + * org.json.JSONArray, java.lang.String) + */ + @Override + public PluginResult execute(final String action, final JSONArray data, + final String callBackId) { + Log.d("DatePickerPlugin", "Plugin Called"); + PluginResult result = null; + + if (ACTION_DATE.equalsIgnoreCase(action)) { + Log.d("DatePickerPluginListener execute", ACTION_DATE); + this.showDatePicker(callBackId); + final PluginResult r = new PluginResult( + PluginResult.Status.NO_RESULT); + r.setKeepCallback(true); + return r; + + } else if (ACTION_TIME.equalsIgnoreCase(action)) { + Log.d("DatePickerPluginListener execute", ACTION_TIME); + this.showTimePicker(callBackId); + final PluginResult r = new PluginResult( + PluginResult.Status.NO_RESULT); + r.setKeepCallback(true); + return r; + + } else { + result = new PluginResult(Status.INVALID_ACTION); + Log.d("DatePickerPlugin", "Invalid action : " + action + " passed"); + } + + return result; + } + + public synchronized void showTimePicker(final String callBackId) { + final DatePickerPlugin datePickerPlugin = this; + final PhonegapActivity currentCtx = ctx; + + final Runnable runnable = new Runnable() { + + public void run() { + final TimePickerDialog tpd = new TimePickerDialog(currentCtx, + new OnTimeSetListener() { + + public void onTimeSet(final TimePicker view, + final int hourOfDay, final int minute) { + final JSONObject userChoice = new JSONObject(); + try { + userChoice.put("hour", hourOfDay); + userChoice.put("min", minute); + } catch (final JSONException jsonEx) { + Log.e("showDatePicker", + "Got JSON Exception " + + jsonEx.getMessage()); + datePickerPlugin.error(new PluginResult( + Status.JSON_EXCEPTION), callBackId); + } + datePickerPlugin.success(new PluginResult( + PluginResult.Status.OK, userChoice), + callBackId); + + } + }, 1, 1, true); + + tpd.show(); + } + }; + ctx.runOnUiThread(runnable); + + } + + public synchronized void showDatePicker(final String callBackId) { + + final DatePickerPlugin datePickerPlugin = this; + final PhonegapActivity currentCtx = ctx; + final Calendar c = Calendar.getInstance(); + final int mYear = c.get(Calendar.YEAR); + final int mMonth = c.get(Calendar.MONTH); + final int mDay = c.get(Calendar.DAY_OF_MONTH); + + final Runnable runnable = new Runnable() { + + public void run() { + final DatePickerDialog dpd = new DatePickerDialog(currentCtx, + new OnDateSetListener() { + + public void onDateSet(final DatePicker view, + final int year, final int monthOfYear, + final int dayOfMonth) { + + final JSONObject userChoice = new JSONObject(); + + try { + userChoice.put("year", year); + userChoice.put("month", monthOfYear); + userChoice.put("day", dayOfMonth); + } catch (final JSONException jsonEx) { + Log.e("showDatePicker", + "Got JSON Exception " + + jsonEx.getMessage()); + datePickerPlugin.error(new PluginResult( + Status.JSON_EXCEPTION), callBackId); + } + + datePickerPlugin.success(new PluginResult( + PluginResult.Status.OK, userChoice), + callBackId); + + } + }, mYear, mMonth, mDay); + + dpd.show(); + } + }; + ctx.runOnUiThread(runnable); + } + +} diff --git a/Android/DateTimePicker/README.txt b/Android/DateTimePicker/README.txt new file mode 100644 index 00000000..50bf857e --- /dev/null +++ b/Android/DateTimePicker/README.txt @@ -0,0 +1,50 @@ +This plugin allows you to leave the PhoneGap webview and enter into the native android date and time picker. +Once the user has selected time or date, they will be sent back into the PhoneGap webview with selected value available. + + +How to use: + +Usage: + +window.plugins.datePickerPlugin.showDateOrTime(dataType,successCallback,errorCallback); + +dataType argument takes two value: + 'date' : if you need the datePicker + 'time' : if you need the timePicker + +For the date picker : Success returns an object with the parameters {day,month,year} filled with selected values. +For the time picker : Success returns an object with the parameters {hour,min} filled with selected values. + +Example: + + document.querySelector("#mypickdatebutton").addEventListener("tap", function() { + window.plugins.datePickerPlugin.showDateOrTime( + 'date', + function(r){ + document.getElementById("mydatetargetfield").value = r.day + "/" + r.month + "/" + r.year; + }, + function(e){console.log(e);} + ); + }, false); + + document.querySelector("#mypickdatebutton").addEventListener("tap", function() { + window.plugins.datePickerPlugin.showDateOrTime( + 'time', + function(r){ + document.getElementById("mytimetargetfield").value = r.hour + "h" + r.min; + }, + function(e){ + console.log(e); + } + ); + }, false); + + + +For the current files to work, you'll need to create a package (folders) called com.ngapplication.plugin. +You can change this to whatever you like, just update the datePickerPlugin.js and datePickerPlugin.java. + +datePickerPlugin.js should go in the asset folder and should be referenced in your index.html file. + + +Limitations: diff --git a/Android/DateTimePicker/datePickerPlugin.js b/Android/DateTimePicker/datePickerPlugin.js new file mode 100644 index 00000000..190ee747 --- /dev/null +++ b/Android/DateTimePicker/datePickerPlugin.js @@ -0,0 +1,30 @@ +/** + * + * @return Object literal singleton instance of DatePicker + */ +var DatePicker = function() { + +}; + +DatePicker.prototype.showDateOrTime = function(action,successCallback, failureCallback) { + return PhoneGap.exec( + successCallback, //Success callback from the plugin + failureCallback, //Error callback from the plugin + 'DatePickerPlugin', //Tell PhoneGap to run "DatePickerPlugin" Plugin + action, //Tell plugin, which action we want to perform + []); //Passing list of args to the plugin +}; + +/** + * Enregistre une nouvelle bibliothèque de fonctions + * auprès de PhoneGap + **/ + +PhoneGap.addConstructor(function() { + //Register the javascript plugin with PhoneGap + PhoneGap.addPlugin('datePickerPlugin', new DatePicker()); + + //Register the native class of plugin with PhoneGap + PluginManager.addService("DatePickerPlugin", + "com.ngapplication.plugin.DatePickerPlugin"); +}); \ No newline at end of file diff --git a/Android/Facebook/README.md b/Android/Facebook/README.md new file mode 100644 index 00000000..2602fc14 --- /dev/null +++ b/Android/Facebook/README.md @@ -0,0 +1,36 @@ +# Facebook for PhoneGap on Android # +by Jos Shepherd + +This is an attempt to make a PhoneGap plugin from the Facebook Android SDK: +https://github.com/facebook/facebook-android-sdk + +It is currently not functional (the login dialog is silently failing to appear) + + +## Licence ## + +The MIT License + +Copyright (c) 2010 Jos Shepherd + +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. + + + + diff --git a/Android/Facebook/src/com/facebook/android/AsyncFacebookRunner.java b/Android/Facebook/src/com/facebook/android/AsyncFacebookRunner.java new file mode 100644 index 00000000..d5d07efa --- /dev/null +++ b/Android/Facebook/src/com/facebook/android/AsyncFacebookRunner.java @@ -0,0 +1,268 @@ +/* + * Copyright 2010 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.facebook.android; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; + +import android.content.Context; +import android.os.Bundle; + +/** + * A sample implementation of asynchronous API requests. This class provides + * the ability to execute API methods and have the call return immediately, + * without blocking the calling thread. This is necessary when accessing the + * API in the UI thread, for instance. The request response is returned to + * the caller via a callback interface, which the developer must implement. + * + * This sample implementation simply spawns a new thread for each request, + * and makes the API call immediately. This may work in many applications, + * but more sophisticated users may re-implement this behavior using a thread + * pool, a network thread, a request queue, or other mechanism. Advanced + * functionality could be built, such as rate-limiting of requests, as per + * a specific application's needs. + * + * @see RequestListener + * The callback interface. + * + * @author ssoneff@facebook.com + * + */ +public class AsyncFacebookRunner { + + Facebook fb; + + public AsyncFacebookRunner(Facebook fb) { + this.fb = fb; + } + + /** + * Invalidate the current user session by removing the access token in + * memory, clearing the browser cookies, and calling auth.expireSession + * through the API. The application will be notified when logout is + * complete via the callback interface. + * + * Note that this method is asynchronous and the callback will be invoked + * in a background thread; operations that affect the UI will need to be + * posted to the UI thread or an appropriate handler. + * + * @param context + * The Android context in which the logout should be called: it + * should be the same context in which the login occurred in + * order to clear any stored cookies + * @param listener + * Callback interface to notify the application when the request + * has completed. + */ + public void logout(final Context context, final RequestListener listener) { + new Thread() { + @Override public void run() { + try { + String response = fb.logout(context); + if (response.length() == 0 || response.equals("false")){ + listener.onFacebookError(new FacebookError( + "auth.expireSession failed")); + return; + } + listener.onComplete(response); + } catch (FileNotFoundException e) { + listener.onFileNotFoundException(e); + } catch (MalformedURLException e) { + listener.onMalformedURLException(e); + } catch (IOException e) { + listener.onIOException(e); + } + } + }.start(); + } + + /** + * Make a request to Facebook's old (pre-graph) API with the given + * parameters. One of the parameter keys must be "method" and its value + * should be a valid REST server API method. + * + * + * See http://developers.facebook.com/docs/reference/rest/ + * + * Note that this method is asynchronous and the callback will be invoked + * in a background thread; operations that affect the UI will need to be + * posted to the UI thread or an appropriate handler. + * + * Example: + * + * Bundle parameters = new Bundle(); + * parameters.putString("method", "auth.expireSession", new Listener()); + * String response = request(parameters); + * + * + * @param parameters + * Key-value pairs of parameters to the request. Refer to the + * documentation: one of the parameters must be "method". + * @param listener + * Callback interface to notify the application when the request + * has completed. + */ + public void request(Bundle parameters, + RequestListener listener) { + request(null, parameters, "GET", listener); + } + + /** + * Make a request to the Facebook Graph API without any parameters. + * + * See http://developers.facebook.com/docs/api + * + * Note that this method is asynchronous and the callback will be invoked + * in a background thread; operations that affect the UI will need to be + * posted to the UI thread or an appropriate handler. + * + * @param graphPath + * Path to resource in the Facebook graph, e.g., to fetch data + * about the currently logged authenticated user, provide "me", + * which will fetch http://graph.facebook.com/me + * @param listener + * Callback interface to notify the application when the request + * has completed. + */ + public void request(String graphPath, + RequestListener listener) { + request(graphPath, new Bundle(), "GET", listener); + } + + /** + * Make a request to the Facebook Graph API with the given string parameters + * using an HTTP GET (default method). + * + * See http://developers.facebook.com/docs/api + * + * Note that this method is asynchronous and the callback will be invoked + * in a background thread; operations that affect the UI will need to be + * posted to the UI thread or an appropriate handler. + * + * @param graphPath + * Path to resource in the Facebook graph, e.g., to fetch data + * about the currently logged authenticated user, provide "me", + * which will fetch http://graph.facebook.com/me + * @param parameters + * key-value string parameters, e.g. the path "search" with + * parameters "q" : "facebook" would produce a query for the + * following graph resource: + * https://graph.facebook.com/search?q=facebook + * @param listener + * Callback interface to notify the application when the request + * has completed. + */ + public void request(String graphPath, + Bundle parameters, + RequestListener listener) { + request(graphPath, parameters, "GET", listener); + } + + /** + * Make a request to the Facebook Graph API with the given HTTP method and + * string parameters. Note that binary data parameters (e.g. pictures) are + * not yet supported by this helper function. + * + * See http://developers.facebook.com/docs/api + * + * Note that this method is asynchronous and the callback will be invoked + * in a background thread; operations that affect the UI will need to be + * posted to the UI thread or an appropriate handler. + * + * @param graphPath + * Path to resource in the Facebook graph, e.g., to fetch data + * about the currently logged authenticated user, provide "me", + * which will fetch http://graph.facebook.com/me + * @param parameters + * key-value string parameters, e.g. the path "search" with + * parameters {"q" : "facebook"} would produce a query for the + * following graph resource: + * https://graph.facebook.com/search?q=facebook + * @param httpMethod + * http verb, e.g. "POST", "DELETE" + * @param listener + * Callback interface to notify the application when the request + * has completed. + */ + public void request(final String graphPath, + final Bundle parameters, + final String httpMethod, + final RequestListener listener) { + new Thread() { + @Override public void run() { + try { + String resp = fb.request(graphPath, parameters, httpMethod); + listener.onComplete(resp); + } catch (FileNotFoundException e) { + listener.onFileNotFoundException(e); + } catch (MalformedURLException e) { + listener.onMalformedURLException(e); + } catch (IOException e) { + listener.onIOException(e); + } + } + }.start(); + } + + + /** + * Callback interface for API requests. + * + */ + public static interface RequestListener { + + /** + * Called when a request completes with the given response. + * + * Executed by a background thread: do not update the UI in this method. + */ + public void onComplete(String response); + + /** + * Called when a request has a network or request error. + * + * Executed by a background thread: do not update the UI in this method. + */ + public void onIOException(IOException e); + + /** + * Called when a request fails because the requested resource is + * invalid or does not exist. + * + * Executed by a background thread: do not update the UI in this method. + */ + public void onFileNotFoundException(FileNotFoundException e); + + /** + * Called if an invalid graph path is provided (which may result in a + * malformed URL). + * + * Executed by a background thread: do not update the UI in this method. + */ + public void onMalformedURLException(MalformedURLException e); + + /** + * Called when the server-side Facebook method fails. + * + * Executed by a background thread: do not update the UI in this method. + */ + public void onFacebookError(FacebookError e); + + } + +} diff --git a/Android/Facebook/src/com/facebook/android/DialogError.java b/Android/Facebook/src/com/facebook/android/DialogError.java new file mode 100644 index 00000000..ff50a940 --- /dev/null +++ b/Android/Facebook/src/com/facebook/android/DialogError.java @@ -0,0 +1,51 @@ +/* + * Copyright 2010 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.facebook.android; + +/** + * Encapsulation of Dialog Error. + * + * @author ssoneff@facebook.com + */ +public class DialogError extends Throwable { + + private static final long serialVersionUID = 1L; + + /** + * The ErrorCode received by the WebView: see + * http://developer.android.com/reference/android/webkit/WebViewClient.html + */ + private int mErrorCode; + + /** The URL that the dialog was trying to load */ + private String mFailingUrl; + + public DialogError(String message, int errorCode, String failingUrl) { + super(message); + mErrorCode = errorCode; + mFailingUrl = failingUrl; + } + + int getErrorCode() { + return mErrorCode; + } + + String getFailingUrl() { + return mFailingUrl; + } + +} diff --git a/Android/Facebook/src/com/facebook/android/Facebook.java b/Android/Facebook/src/com/facebook/android/Facebook.java new file mode 100644 index 00000000..20ac15fd --- /dev/null +++ b/Android/Facebook/src/com/facebook/android/Facebook.java @@ -0,0 +1,756 @@ +/* + * Copyright 2010 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.facebook.android; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; + +import android.Manifest; +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.Signature; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.webkit.CookieSyncManager; + +/** + * Main Facebook object for interacting with the Facebook developer API. + * Provides methods to log in and log out a user, make requests using the REST + * and Graph APIs, and start user interface interactions with the API (such as + * pop-ups promoting for credentials, permissions, stream posts, etc.) + * + * @author Steven Soneff (ssoneff@facebook.com), Jim Brusstar + * (jimbru@facebook.com), Yariv Sadan (yariv@facebook.com), + * Luke Shepard (lshepard@facebook.com) + */ +public class Facebook { + + /* Strings used in the OAuth flow */ + public static final String REDIRECT_URI = "fbconnect://success"; + public static final String CANCEL_URI = "fbconnect://cancel"; + public static final String TOKEN = "access_token"; + public static final String EXPIRES = "expires_in"; + public static final String SINGLE_SIGN_ON_DISABLED = "service_disabled"; + + public static final int FORCE_DIALOG_AUTH = -1; + + private static final String LOGIN = "login"; + + // Used as default activityCode by authorize(). See authorize() below. + private static final int DEFAULT_AUTH_ACTIVITY_CODE = 32665; + + /* Facebook server endpoints: may be modified in a subclass for testing */ + protected static String OAUTH_ENDPOINT = + "https://www.facebook.com/dialog/oauth"; + protected static String UI_SERVER = + "https://www.facebook.com/connect/uiserver.php"; + protected static String GRAPH_BASE_URL = + "https://graph.facebook.com/"; + protected static String RESTSERVER_URL = + "https://api.facebook.com/restserver.php"; + + private String mAccessToken = null; + private long mAccessExpires = 0; + private String mAppId; + + private Activity mAuthActivity; + private String[] mAuthPermissions; + private int mAuthActivityCode; + private DialogListener mAuthDialogListener; + + public Facebook(String applicationId) { + if (applicationId == null) { + throw new IllegalArgumentException( + "You must specify your application ID when instantiating a Facebook " + + "object. See README for details."); + } + mAppId = applicationId; + } + + /** + * Default authorize method doesn't require any permissions. + */ + public void authorize(Activity activity, final DialogListener listener) { + authorize(activity, new String[] {}, DEFAULT_AUTH_ACTIVITY_CODE, + listener); + } + + /** + * Overloaded signature. See authorize() below. + */ + public void authorize(Activity activity, String[] permissions, + final DialogListener listener) { + authorize(activity, permissions, DEFAULT_AUTH_ACTIVITY_CODE, listener); + } + + /** + * Starts either an activity or a dialog which prompts the user to log in to + * Facebook and grant the requested permissions to the given application. + * + * This method will, when possible, use Facebook's single sign-on for + * Android to obtain an access token. This involves proxying a call through + * the Facebook for Android stand-alone application, which will handle the + * authentication flow, and return an OAuth access token for making API + * calls. + * + * Because this process will not be available for all users, if single + * sign-on is not possible, this method will automatically fall back to the + * OAuth 2.0 User-Agent flow. In this flow, the user credentials are handled + * by Facebook in an embedded WebView, not by the client application. As + * such, the dialog makes a network request and renders HTML content rather + * than a native UI. The access token is retrieved from a redirect to a + * special URL that the WebView handles. + * + * Note that User credentials could be handled natively using the OAuth 2.0 + * Username and Password Flow, but this is not supported by this SDK. + * + * See http://developers.facebook.com/docs/authentication/ and + * http://wiki.oauth.net/OAuth-2 for more details. + * + * Note that this method is asynchronous and the callback will be invoked in + * the original calling thread (not in a background thread). + * + * Also note that requests may be made to the API without calling authorize + * first, in which case only public information is returned. + * + * IMPORTANT: Note that single sign-on authentication will not function + * correctly if you do not include a call to the authorizeCallback() method + * in your onActivityResult() function! Please see below for more + * information. single sign-on may be disabled by passing FORCE_DIALOG_AUTH + * as the activityCode parameter in your call to authorize(). + * + * @param activity + * The Android activity in which we want to display the + * authorization dialog. + * @param applicationId + * The Facebook application identifier e.g. "350685531728" + * @param permissions + * A list of permissions required for this application: e.g. + * "read_stream", "publish_stream", "offline_access", etc. see + * http://developers.facebook.com/docs/authentication/permissions + * This parameter should not be null -- if you do not require any + * permissions, then pass in an empty String array. + * @param activityCode + * Single sign-on requires an activity result to be called back + * to the client application -- if you are waiting on other + * activities to return data, pass a custom activity code here to + * avoid collisions. If you would like to force the use of legacy + * dialog-based authorization, pass FORCE_DIALOG_AUTH for this + * parameter. Otherwise just omit this parameter and Facebook + * will use a suitable default. See + * http://developer.android.com/reference/android/ + * app/Activity.html for more information. + * @param listener + * Callback interface for notifying the calling application when + * the authentication dialog has completed, failed, or been + * canceled. + */ + public void authorize(Activity activity, String[] permissions, + int activityCode, final DialogListener listener) { + + boolean singleSignOnStarted = false; + + mAuthDialogListener = listener; + + // Prefer single sign-on, where available. + if (activityCode >= 0) { + singleSignOnStarted = startSingleSignOn(activity, mAppId, + permissions, activityCode); + } + // Otherwise fall back to traditional dialog. + if (!singleSignOnStarted) { + + startDialogAuth(activity, mAppId, permissions); + } + } + + /** + * Internal method to handle single sign-on backend for authorize(). + * + * @param activity + * The Android Activity that will parent the ProxyAuth Activity. + * @param applicationId + * The Facebook application identifier. + * @param permissions + * A list of permissions required for this application. If you do + * not require any permissions, pass an empty String array. + * @param activityCode + * Activity code to uniquely identify the result Intent in the + * callback. + */ + private boolean startSingleSignOn(Activity activity, String applicationId, + String[] permissions, int activityCode) { + boolean didSucceed = true; + Intent intent = new Intent(); + + intent.setClassName("com.facebook.katana", + "com.facebook.katana.ProxyAuth"); + intent.putExtra("client_id", applicationId); + if (permissions.length > 0) { + intent.putExtra("scope", TextUtils.join(",", permissions)); + } + + // Verify that the application whose package name is + // com.facebook.katana.ProxyAuth + // has the expected FB app signature. + if (!validateAppSignatureForIntent(activity, intent)) { + return false; + } + + mAuthActivity = activity; + mAuthPermissions = permissions; + mAuthActivityCode = activityCode; + try { + activity.startActivityForResult(intent, activityCode); + } catch (ActivityNotFoundException e) { + didSucceed = false; + } + + return didSucceed; + } + + /** + * Query the signature for the application that would be invoked by the + * given intent and verify that it matches the FB application's signature. + * + * @param activity + * @param intent + * @param validSignature + * @return true if the app's signature matches the expected signature. + */ + private boolean validateAppSignatureForIntent(Activity activity, + Intent intent) { + + ResolveInfo resolveInfo = activity.getPackageManager().resolveActivity( + intent, 0); + if (resolveInfo == null) { + return false; + } + + String packageName = resolveInfo.activityInfo.packageName; + PackageInfo packageInfo; + try { + packageInfo = activity.getPackageManager().getPackageInfo( + packageName, PackageManager.GET_SIGNATURES); + } catch (NameNotFoundException e) { + return false; + } + + for (Signature signature : packageInfo.signatures) { + if (signature.toCharsString().equals(FB_APP_SIGNATURE)) { + return true; + } + } + return false; + } + + /** + * Internal method to handle dialog-based authentication backend for + * authorize(). + * + * @param activity + * The Android Activity that will parent the auth dialog. + * @param applicationId + * The Facebook application identifier. + * @param permissions + * A list of permissions required for this application. If you do + * not require any permissions, pass an empty String array. + */ + private void startDialogAuth(Activity activity, String applicationId, + String[] permissions) { + Bundle params = new Bundle(); + params.putString("client_id", applicationId); + if (permissions.length > 0) { + params.putString("scope", TextUtils.join(",", permissions)); + } + + CookieSyncManager.createInstance(activity); + dialog(activity, LOGIN, params, new DialogListener() { + + public void onComplete(Bundle values) { + // ensure any cookies set by the dialog are saved + CookieSyncManager.getInstance().sync(); + setAccessToken(values.getString(TOKEN)); + setAccessExpiresIn(values.getString(EXPIRES)); + if (isSessionValid()) { + Log.d("Facebook-authorize", "Login Success! access_token=" + + getAccessToken() + " expires=" + + getAccessExpires()); + mAuthDialogListener.onComplete(values); + } else { + mAuthDialogListener.onFacebookError(new FacebookError( + "Failed to receive access token.")); + } + } + + public void onError(DialogError error) { + Log.d("Facebook-authorize", "Login failed: " + error); + mAuthDialogListener.onError(error); + } + + public void onFacebookError(FacebookError error) { + Log.d("Facebook-authorize", "Login failed: " + error); + mAuthDialogListener.onFacebookError(error); + } + + public void onCancel() { + Log.d("Facebook-authorize", "Login canceled"); + mAuthDialogListener.onCancel(); + } + }); + } + + /** + * IMPORTANT: This method must be invoked at the top of the calling + * activity's onActivityResult() function or Facebook authentication will + * not function properly! + * + * If your calling activity does not currently implement onActivityResult(), + * you must implement it and include a call to this method if you intend to + * use the authorize() method in this SDK. + * + * For more information, see + * http://developer.android.com/reference/android/app/ + * Activity.html#onActivityResult(int, int, android.content.Intent) + */ + public void authorizeCallback(int requestCode, int resultCode, Intent data) { + if (requestCode == mAuthActivityCode) { + + // Successfully redirected. + if (resultCode == Activity.RESULT_OK) { + + // Check OAuth 2.0/2.10 error code. + String error = data.getStringExtra("error"); + if (error == null) { + error = data.getStringExtra("error_type"); + } + + // A Facebook error occurred. + if (error != null) { + if (error.equals(SINGLE_SIGN_ON_DISABLED) + || error.equals("AndroidAuthKillSwitchException")) { + Log.d("Facebook-authorize", "Hosted auth currently " + + "disabled. Retrying dialog auth..."); + startDialogAuth(mAuthActivity, mAppId, + mAuthPermissions); + } else if (error.equals("access_denied") + || error.equals("OAuthAccessDeniedException")) { + Log.d("Facebook-authorize", "Login canceled by user."); + mAuthDialogListener.onCancel(); + } else { + Log.d("Facebook-authorize", "Login failed: " + error); + mAuthDialogListener.onFacebookError(new FacebookError( + error)); + } + + // No errors. + } else { + setAccessToken(data.getStringExtra(TOKEN)); + setAccessExpiresIn(data.getStringExtra(EXPIRES)); + if (isSessionValid()) { + Log.d("Facebook-authorize", + "Login Success! access_token=" + + getAccessToken() + " expires=" + + getAccessExpires()); + mAuthDialogListener.onComplete(data.getExtras()); + } else { + mAuthDialogListener.onFacebookError(new FacebookError( + "Failed to receive access token.")); + } + } + + // An error occurred before we could be redirected. + } else if (resultCode == Activity.RESULT_CANCELED) { + + // An Android error occured. + if (data != null) { + Log.d("Facebook-authorize", + "Login failed: " + data.getStringExtra("error")); + mAuthDialogListener.onError(new DialogError(data + .getStringExtra("error"), data.getIntExtra( + "error_code", -1), data + .getStringExtra("failing_url"))); + + // User pressed the 'back' button. + } else { + Log.d("Facebook-authorize", "Login canceled by user."); + mAuthDialogListener.onCancel(); + } + } + } + } + + /** + * Invalidate the current user session by removing the access token in + * memory, clearing the browser cookie, and calling auth.expireSession + * through the API. + * + * Note that this method blocks waiting for a network response, so do not + * call it in a UI thread. + * + * @param context + * The Android context in which the logout should be called: it + * should be the same context in which the login occurred in + * order to clear any stored cookies + * @throws IOException + * @throws MalformedURLException + * @return JSON string representation of the auth.expireSession response + * ("true" if successful) + */ + public String logout(Context context) throws MalformedURLException, + IOException { + Util.clearCookies(context); + Bundle b = new Bundle(); + b.putString("method", "auth.expireSession"); + String response = request(b); + setAccessToken(null); + setAccessExpires(0); + return response; + } + + /** + * Make a request to Facebook's old (pre-graph) API with the given + * parameters. One of the parameter keys must be "method" and its value + * should be a valid REST server API method. + * + * See http://developers.facebook.com/docs/reference/rest/ + * + * Note that this method blocks waiting for a network response, so do not + * call it in a UI thread. + * + * Example: + * Bundle parameters = new Bundle(); + * parameters.putString("method", "auth.expireSession"); + * String response = request(parameters); + * + * + * @param parameters + * Key-value pairs of parameters to the request. Refer to the + * documentation: one of the parameters must be "method". + * @throws IOException + * if a network error occurs + * @throws MalformedURLException + * if accessing an invalid endpoint + * @throws IllegalArgumentException + * if one of the parameters is not "method" + * @return JSON string representation of the response + */ + public String request(Bundle parameters) throws MalformedURLException, + IOException { + if (!parameters.containsKey("method")) { + throw new IllegalArgumentException("API method must be specified. " + + "(parameters must contain key \"method\" and value). See" + + " http://developers.facebook.com/docs/reference/rest/"); + } + return request(null, parameters, "GET"); + } + + /** + * Make a request to the Facebook Graph API without any parameters. + * + * See http://developers.facebook.com/docs/api + * + * Note that this method blocks waiting for a network response, so do not + * call it in a UI thread. + * + * @param graphPath + * Path to resource in the Facebook graph, e.g., to fetch data + * about the currently logged authenticated user, provide "me", + * which will fetch http://graph.facebook.com/me + * @throws IOException + * @throws MalformedURLException + * @return JSON string representation of the response + */ + public String request(String graphPath) throws MalformedURLException, + IOException { + return request(graphPath, new Bundle(), "GET"); + } + + /** + * Make a request to the Facebook Graph API with the given string parameters + * using an HTTP GET (default method). + * + * See http://developers.facebook.com/docs/api + * + * Note that this method blocks waiting for a network response, so do not + * call it in a UI thread. + * + * @param graphPath + * Path to resource in the Facebook graph, e.g., to fetch data + * about the currently logged authenticated user, provide "me", + * which will fetch http://graph.facebook.com/me + * @param parameters + * key-value string parameters, e.g. the path "search" with + * parameters "q" : "facebook" would produce a query for the + * following graph resource: + * https://graph.facebook.com/search?q=facebook + * @throws IOException + * @throws MalformedURLException + * @return JSON string representation of the response + */ + public String request(String graphPath, Bundle parameters) + throws MalformedURLException, IOException { + return request(graphPath, parameters, "GET"); + } + + /** + * Synchronously make a request to the Facebook Graph API with the given + * HTTP method and string parameters. Note that binary data parameters (e.g. + * pictures) are not yet supported by this helper function. + * + * See http://developers.facebook.com/docs/api + * + * Note that this method blocks waiting for a network response, so do not + * call it in a UI thread. + * + * @param graphPath + * Path to resource in the Facebook graph, e.g., to fetch data + * about the currently logged authenticated user, provide "me", + * which will fetch http://graph.facebook.com/me + * @param parameters + * key-value string parameters, e.g. the path "search" with + * parameters {"q" : "facebook"} would produce a query for the + * following graph resource: + * https://graph.facebook.com/search?q=facebook + * @param httpMethod + * http verb, e.g. "GET", "POST", "DELETE" + * @throws IOException + * @throws MalformedURLException + * @return JSON string representation of the response + */ + public String request(String graphPath, Bundle parameters, String httpMethod) + throws FileNotFoundException, MalformedURLException, IOException { + parameters.putString("format", "json"); + if (isSessionValid()) { + parameters.putString(TOKEN, getAccessToken()); + } + String url = graphPath != null ? GRAPH_BASE_URL + graphPath + : RESTSERVER_URL; + return Util.openUrl(url, httpMethod, parameters); + } + + /** + * Generate a UI dialog for the request action in the given Android context. + * + * Note that this method is asynchronous and the callback will be invoked in + * the original calling thread (not in a background thread). + * + * @param context + * The Android context in which we will generate this dialog. + * @param action + * String representation of the desired method: e.g. "login", + * "stream.publish", ... + * @param listener + * Callback interface to notify the application when the dialog + * has completed. + */ + public void dialog(Context context, String action, DialogListener listener) { + dialog(context, action, new Bundle(), listener); + } + + /** + * Generate a UI dialog for the request action in the given Android context + * with the provided parameters. + * + * Note that this method is asynchronous and the callback will be invoked in + * the original calling thread (not in a background thread). + * + * @param context + * The Android context in which we will generate this dialog. + * @param action + * String representation of the desired method: e.g. "login", + * "stream.publish", ... + * @param parameters + * key-value string parameters + * @param listener + * Callback interface to notify the application when the dialog + * has completed. + */ + public void dialog(Context context, String action, Bundle parameters, + final DialogListener listener) { + String endpoint; + if (action.equals(LOGIN)) { + endpoint = OAUTH_ENDPOINT; + parameters.putString("type", "user_agent"); + parameters.putString("redirect_uri", REDIRECT_URI); + } else { + endpoint = UI_SERVER; + parameters.putString("method", action); + parameters.putString("next", REDIRECT_URI); + } + parameters.putString("display", "touch"); + if (isSessionValid()) { + parameters.putString(TOKEN, getAccessToken()); + } + String url = endpoint + "?" + Util.encodeUrl(parameters); + + if (context.checkCallingOrSelfPermission(Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED) { + Util.showAlert(context, "Error", + "Application requires permission to access the Internet"); + } else { + System.out.println("FbDialog url: "+url); + + FbDialog test = new FbDialog(context, url, listener); + System.out.println("Constructed"); + + test.show(); + } + } + + /** + * @return boolean - whether this object has an non-expired session token + */ + public boolean isSessionValid() { + return (getAccessToken() != null) + && ((getAccessExpires() == 0) || (System.currentTimeMillis() < getAccessExpires())); + } + + /** + * Retrieve the OAuth 2.0 access token for API access: treat with care. + * Returns null if no session exists. + * + * @return String - access token + */ + public String getAccessToken() { + return mAccessToken; + } + + /** + * Retrieve the current session's expiration time (in milliseconds since + * Unix epoch), or 0 if the session doesn't expire or doesn't exist. + * + * @return long - session expiration time + */ + public long getAccessExpires() { + return mAccessExpires; + } + + /** + * Set the OAuth 2.0 access token for API access. + * + * @param token + * - access token + */ + public void setAccessToken(String token) { + mAccessToken = token; + } + + /** + * Set the current session's expiration time (in milliseconds since Unix + * epoch), or 0 if the session doesn't expire. + * + * @param time + * - timestamp in milliseconds + */ + public void setAccessExpires(long time) { + mAccessExpires = time; + } + + /** + * Set the current session's duration (in seconds since Unix epoch). + * + * @param expiresIn + * - duration in seconds + */ + public void setAccessExpiresIn(String expiresIn) { + if (expiresIn != null && !expiresIn.equals("0")) { + setAccessExpires(System.currentTimeMillis() + + Integer.parseInt(expiresIn) * 1000); + } + } + + public String getAppId() { + return mAppId; + } + + public void setAppId(String appId) { + mAppId = appId; + } + + /** + * Callback interface for dialog requests. + * + */ + public static interface DialogListener { + + /** + * Called when a dialog completes. + * + * Executed by the thread that initiated the dialog. + * + * @param values + * Key-value string pairs extracted from the response. + */ + public void onComplete(Bundle values); + + /** + * Called when a Facebook responds to a dialog with an error. + * + * Executed by the thread that initiated the dialog. + * + */ + public void onFacebookError(FacebookError e); + + /** + * Called when a dialog has an error. + * + * Executed by the thread that initiated the dialog. + * + */ + public void onError(DialogError e); + + /** + * Called when a dialog is canceled by the user. + * + * Executed by the thread that initiated the dialog. + * + */ + public void onCancel(); + + } + + public static final String FB_APP_SIGNATURE = + "30820268308201d102044a9c4610300d06092a864886f70d0101040500307a310" + + "b3009060355040613025553310b30090603550408130243413112301006035504" + + "07130950616c6f20416c746f31183016060355040a130f46616365626f6f6b204" + + "d6f62696c653111300f060355040b130846616365626f6f6b311d301b06035504" + + "03131446616365626f6f6b20436f72706f726174696f6e3020170d30393038333" + + "13231353231365a180f32303530303932353231353231365a307a310b30090603" + + "55040613025553310b30090603550408130243413112301006035504071309506" + + "16c6f20416c746f31183016060355040a130f46616365626f6f6b204d6f62696c" + + "653111300f060355040b130846616365626f6f6b311d301b06035504031314466" + + "16365626f6f6b20436f72706f726174696f6e30819f300d06092a864886f70d01" + + "0101050003818d0030818902818100c207d51df8eb8c97d93ba0c8c1002c928fa" + + "b00dc1b42fca5e66e99cc3023ed2d214d822bc59e8e35ddcf5f44c7ae8ade50d7" + + "e0c434f500e6c131f4a2834f987fc46406115de2018ebbb0d5a3c261bd97581cc" + + "fef76afc7135a6d59e8855ecd7eacc8f8737e794c60a761c536b72b11fac8e603" + + "f5da1a2d54aa103b8a13c0dbc10203010001300d06092a864886f70d010104050" + + "0038181005ee9be8bcbb250648d3b741290a82a1c9dc2e76a0af2f2228f1d9f9c" + + "4007529c446a70175c5a900d5141812866db46be6559e2141616483998211f4a6" + + "73149fb2232a10d247663b26a9031e15f84bc1c74d141ff98a02d76f85b2c8ab2" + + "571b6469b232d8e768a7f7ca04f7abe4a775615916c07940656b58717457b42bd" + + "928a2"; + +} diff --git a/Android/Facebook/src/com/facebook/android/FacebookError.java b/Android/Facebook/src/com/facebook/android/FacebookError.java new file mode 100644 index 00000000..c98bed94 --- /dev/null +++ b/Android/Facebook/src/com/facebook/android/FacebookError.java @@ -0,0 +1,49 @@ +/* + * Copyright 2010 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.facebook.android; + +/** + * Encapsulation of a Facebook Error: a Facebook request that could not be + * fulfilled. + * + * @author ssoneff@facebook.com + */ +public class FacebookError extends Throwable { + + private static final long serialVersionUID = 1L; + + private int mErrorCode = 0; + private String mErrorType; + + public FacebookError(String message) { + super(message); + } + + public FacebookError(String message, String type, int code) { + super(message); + mErrorType = type; + mErrorCode = code; + } + + public int getErrorCode() { + return mErrorCode; + } + + public String getErrorType() { + return mErrorType; + } +} diff --git a/Android/Facebook/src/com/facebook/android/FbDialog.java b/Android/Facebook/src/com/facebook/android/FbDialog.java new file mode 100644 index 00000000..908b10f5 --- /dev/null +++ b/Android/Facebook/src/com/facebook/android/FbDialog.java @@ -0,0 +1,185 @@ +/* + * Copyright 2010 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.facebook.android; + +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.Display; +import android.view.ViewGroup; +import android.view.Window; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.facebook.android.Facebook.DialogListener; + +public class FbDialog extends Dialog { + + static final int FB_BLUE = 0xFF6D84B4; + static final float[] DIMENSIONS_LANDSCAPE = {460, 260}; + static final float[] DIMENSIONS_PORTRAIT = {280, 420}; + static final FrameLayout.LayoutParams FILL = + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT); + static final int MARGIN = 4; + static final int PADDING = 2; + static final String DISPLAY_STRING = "touch"; + static final String FB_ICON = "icon.png"; + + private String mUrl; + private DialogListener mListener; + private ProgressDialog mSpinner; + private WebView mWebView; + private LinearLayout mContent; + private TextView mTitle; + + public FbDialog(Context context, String url, DialogListener listener) { + super(context); + Log.d("FbDialog","construct"); + + mUrl = url; + mListener = listener; + + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.d("FbDialog","onCreate"); + + mSpinner = new ProgressDialog(getContext()); + mSpinner.requestWindowFeature(Window.FEATURE_NO_TITLE); + mSpinner.setMessage("Loading..."); + + mContent = new LinearLayout(getContext()); + mContent.setOrientation(LinearLayout.VERTICAL); + setUpTitle(); + setUpWebView(); + Display display = getWindow().getWindowManager().getDefaultDisplay(); + final float scale = getContext().getResources().getDisplayMetrics().density; + float[] dimensions = display.getWidth() < display.getHeight() ? + DIMENSIONS_PORTRAIT : DIMENSIONS_LANDSCAPE; + addContentView(mContent, new FrameLayout.LayoutParams( + (int) (dimensions[0] * scale + 0.5f), + (int) (dimensions[1] * scale + 0.5f))); + } + + private void setUpTitle() { + + requestWindowFeature(Window.FEATURE_NO_TITLE); +/* Drawable icon = getContext().getResources().getDrawable( + R.drawable.facebook_icon); */ + mTitle = new TextView(getContext()); + mTitle.setText("Facebook"); + mTitle.setTextColor(Color.WHITE); + mTitle.setTypeface(Typeface.DEFAULT_BOLD); + mTitle.setBackgroundColor(FB_BLUE); + mTitle.setPadding(MARGIN + PADDING, MARGIN, MARGIN, MARGIN); + mTitle.setCompoundDrawablePadding(MARGIN + PADDING); +/* mTitle.setCompoundDrawablesWithIntrinsicBounds( + icon, null, null, null); */ + mContent.addView(mTitle); + } + + private void setUpWebView() { + + mWebView = new WebView(getContext()); + mWebView.setVerticalScrollBarEnabled(false); + mWebView.setHorizontalScrollBarEnabled(false); + mWebView.setWebViewClient(new FbDialog.FbWebViewClient()); + mWebView.getSettings().setJavaScriptEnabled(true); + mWebView.loadUrl(mUrl); + mWebView.setLayoutParams(FILL); + mContent.addView(mWebView); + } + + private class FbWebViewClient extends WebViewClient { + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + Log.d("Facebook-WebView", "Redirect URL: " + url); + if (url.startsWith(Facebook.REDIRECT_URI)) { + Bundle values = Util.parseUrl(url); + + String error = values.getString("error"); + if (error == null) { + error = values.getString("error_type"); + } + + if (error == null) { + mListener.onComplete(values); + } else if (error.equals("access_denied") || + error.equals("OAuthAccessDeniedException")) { + mListener.onCancel(); + } else { + mListener.onFacebookError(new FacebookError(error)); + } + + FbDialog.this.dismiss(); + return true; + } else if (url.startsWith(Facebook.CANCEL_URI)) { + mListener.onCancel(); + FbDialog.this.dismiss(); + return true; + } else if (url.contains(DISPLAY_STRING)) { + return false; + } + // launch non-dialog URLs in a full browser + getContext().startActivity( + new Intent(Intent.ACTION_VIEW, Uri.parse(url))); + return true; + } + + @Override + public void onReceivedError(WebView view, int errorCode, + String description, String failingUrl) { + super.onReceivedError(view, errorCode, description, failingUrl); + mListener.onError( + new DialogError(description, errorCode, failingUrl)); + FbDialog.this.dismiss(); + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + Log.d("Facebook-WebView", "Webview loading URL: " + url); + super.onPageStarted(view, url, favicon); + mSpinner.show(); + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + String title = mWebView.getTitle(); + if (title != null && title.length() > 0) { + mTitle.setText(title); + } + mSpinner.dismiss(); + } + + } +} diff --git a/Android/Facebook/src/com/facebook/android/Util.java b/Android/Facebook/src/com/facebook/android/Util.java new file mode 100644 index 00000000..bb90a1d6 --- /dev/null +++ b/Android/Facebook/src/com/facebook/android/Util.java @@ -0,0 +1,299 @@ +/* + * Copyright 2010 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.facebook.android; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; + +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.AlertDialog.Builder; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.webkit.CookieManager; +import android.webkit.CookieSyncManager; + + +/** + * Utility class supporting the Facebook Object. + * + * @author ssoneff@facebook.com + * + */ +public final class Util { + + /** + * Generate the multi-part post body providing the parameters and boundary + * string + * + * @param parameters the parameters need to be posted + * @param boundary the random string as boundary + * @return a string of the post body + */ + public static String encodePostBody(Bundle parameters, String boundary) { + if (parameters == null) return ""; + StringBuilder sb = new StringBuilder(); + + for (String key : parameters.keySet()) { + if (parameters.getByteArray(key) != null) { + continue; + } + + sb.append("Content-Disposition: form-data; name=\"" + key + + "\"\r\n\r\n" + parameters.getString(key)); + sb.append("\r\n" + "--" + boundary + "\r\n"); + } + + return sb.toString(); + } + + public static String encodeUrl(Bundle parameters) { + if (parameters == null) { + return ""; + } + + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (String key : parameters.keySet()) { + if (first) first = false; else sb.append("&"); + sb.append(URLEncoder.encode(key) + "=" + + URLEncoder.encode(parameters.getString(key))); + } + return sb.toString(); + } + + public static Bundle decodeUrl(String s) { + Bundle params = new Bundle(); + if (s != null) { + String array[] = s.split("&"); + for (String parameter : array) { + String v[] = parameter.split("="); + params.putString(URLDecoder.decode(v[0]), + URLDecoder.decode(v[1])); + } + } + return params; + } + + /** + * Parse a URL query and fragment parameters into a key-value bundle. + * + * @param url the URL to parse + * @return a dictionary bundle of keys and values + */ + public static Bundle parseUrl(String url) { + // hack to prevent MalformedURLException + url = url.replace("fbconnect", "http"); + try { + URL u = new URL(url); + Bundle b = decodeUrl(u.getQuery()); + b.putAll(decodeUrl(u.getRef())); + return b; + } catch (MalformedURLException e) { + return new Bundle(); + } + } + + + /** + * Connect to an HTTP URL and return the response as a string. + * + * Note that the HTTP method override is used on non-GET requests. (i.e. + * requests are made as "POST" with method specified in the body). + * + * @param url - the resource to open: must be a welformed URL + * @param method - the HTTP method to use ("GET", "POST", etc.) + * @param params - the query parameter for the URL (e.g. access_token=foo) + * @return the URL contents as a String + * @throws MalformedURLException - if the URL format is invalid + * @throws IOException - if a network problem occurs + */ + public static String openUrl(String url, String method, Bundle params) + throws MalformedURLException, IOException { + // random string as boundary for multi-part http post + String strBoundary = "3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f"; + String endLine = "\r\n"; + + OutputStream os; + + if (method.equals("GET")) { + url = url + "?" + encodeUrl(params); + } + Log.d("Facebook-Util", method + " URL: " + url); + HttpURLConnection conn = + (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestProperty("User-Agent", System.getProperties(). + getProperty("http.agent") + " FacebookAndroidSDK"); + if (!method.equals("GET")) { + Bundle dataparams = new Bundle(); + for (String key : params.keySet()) { + if (params.getByteArray(key) != null) { + dataparams.putByteArray(key, params.getByteArray(key)); + } + } + + // use method override + if (!params.containsKey("method")) { + params.putString("method", method); + } + + if (params.containsKey("access_token")) { + String decoded_token = URLDecoder.decode(params.getString("access_token")); + params.putString("access_token", decoded_token); + } + + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="+strBoundary); + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setRequestProperty("Connection", "Keep-Alive"); + conn.connect(); + os = new BufferedOutputStream(conn.getOutputStream()); + + os.write(("--" + strBoundary +endLine).getBytes()); + os.write((encodePostBody(params, strBoundary)).getBytes()); + os.write((endLine + "--" + strBoundary + endLine).getBytes()); + + if (!dataparams.isEmpty()) { + + for (String key: dataparams.keySet()){ + os.write(("Content-Disposition: form-data; filename=\"" + key + "\"" + endLine).getBytes()); + os.write(("Content-Type: content/unknown" + endLine + endLine).getBytes()); + os.write(dataparams.getByteArray(key)); + os.write((endLine + "--" + strBoundary + endLine).getBytes()); + + } + } + os.flush(); + } + + String response = ""; + try { + response = read(conn.getInputStream()); + } catch (FileNotFoundException e) { + // Error Stream contains JSON that we can parse to a FB error + response = read(conn.getErrorStream()); + } + return response; + } + + private static String read(InputStream in) throws IOException { + StringBuilder sb = new StringBuilder(); + BufferedReader r = new BufferedReader(new InputStreamReader(in), 1000); + for (String line = r.readLine(); line != null; line = r.readLine()) { + sb.append(line); + } + in.close(); + return sb.toString(); + } + + public static void clearCookies(Context context) { + // Edge case: an illegal state exception is thrown if an instance of + // CookieSyncManager has not be created. CookieSyncManager is normally + // created by a WebKit view, but this might happen if you start the + // app, restore saved state, and click logout before running a UI + // dialog in a WebView -- in which case the app crashes + @SuppressWarnings("unused") + CookieSyncManager cookieSyncMngr = + CookieSyncManager.createInstance(context); + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.removeAllCookie(); + } + + /** + * Parse a server response into a JSON Object. This is a basic + * implementation using org.json.JSONObject representation. More + * sophisticated applications may wish to do their own parsing. + * + * The parsed JSON is checked for a variety of error fields and + * a FacebookException is thrown if an error condition is set, + * populated with the error message and error type or code if + * available. + * + * @param response - string representation of the response + * @return the response as a JSON Object + * @throws JSONException - if the response is not valid JSON + * @throws FacebookError - if an error condition is set + */ + public static JSONObject parseJson(String response) + throws JSONException, FacebookError { + // Edge case: when sending a POST request to /[post_id]/likes + // the return value is 'true' or 'false'. Unfortunately + // these values cause the JSONObject constructor to throw + // an exception. + if (response.equals("false")) { + throw new FacebookError("request failed"); + } + if (response.equals("true")) { + response = "{value : true}"; + } + JSONObject json = new JSONObject(response); + + // errors set by the server are not consistent + // they depend on the method and endpoint + if (json.has("error")) { + JSONObject error = json.getJSONObject("error"); + throw new FacebookError( + error.getString("message"), error.getString("type"), 0); + } + if (json.has("error_code") && json.has("error_msg")) { + throw new FacebookError(json.getString("error_msg"), "", + Integer.parseInt(json.getString("error_code"))); + } + if (json.has("error_code")) { + throw new FacebookError("request failed", "", + Integer.parseInt(json.getString("error_code"))); + } + if (json.has("error_msg")) { + throw new FacebookError(json.getString("error_msg")); + } + if (json.has("error_reason")) { + throw new FacebookError(json.getString("error_reason")); + } + return json; + } + + /** + * Display a simple alert dialog with the given text and title. + * + * @param context + * Android context in which the dialog should be displayed + * @param title + * Alert dialog title + * @param text + * Alert dialog message + */ + public static void showAlert(Context context, String title, String text) { + Builder alertBuilder = new Builder(context); + alertBuilder.setTitle(title); + alertBuilder.setMessage(text); + alertBuilder.create().show(); + } + +} diff --git a/Android/Facebook/src/com/phonegap/plugins/facebook/FacebookAuth.java b/Android/Facebook/src/com/phonegap/plugins/facebook/FacebookAuth.java new file mode 100755 index 00000000..50b847ad --- /dev/null +++ b/Android/Facebook/src/com/phonegap/plugins/facebook/FacebookAuth.java @@ -0,0 +1,118 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010, IBM Corporation + */ +package com.phonegap.plugins.facebook; + +import org.json.JSONArray; +import org.json.JSONException; + +import android.content.Intent; +import android.content.Context; +import android.app.Activity; + +import android.net.Uri; +import android.os.Bundle; + +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; + +import com.facebook.android.Facebook; +import com.facebook.android.AsyncFacebookRunner; +import com.facebook.android.Facebook.DialogListener; +import com.facebook.android.DialogError; +import com.facebook.android.FacebookError; + +public class FacebookAuth extends Plugin { + + public static final String APP_ID = "175729095772478"; + private Facebook mFb; + + /** + * Executes the request and returns PluginResult. + * + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @param callbackId The callback id used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args, String callbackId) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + System.out.println("execute: "+ action); + + try { + if (action.equals("authorize")) { + + result = this.authorize(args.getString(0)); + if (result.length() > 0) { + status = PluginResult.Status.ERROR; + } + System.out.println("result: "+ result); + + } + + return new PluginResult(status, result); + } catch (JSONException e) { + System.out.println("exception: "+ action); + + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + + /** + * Identifies if action to be executed returns a value and should be run synchronously. + * + * @param action The action to execute + * @return T=returns value + */ + public boolean isSynch(String action) { + return false; + } + + /** + * Called by AccelBroker when listener is to be shut down. + * Stop listener. + */ + public void onDestroy() { + } + + //-------------------------------------------------------------------------- + // LOCAL METHODS + //-------------------------------------------------------------------------- + + /** + * Display a new browser with the specified URL. + * + * @param url The url to load. + * @param usePhoneGap Load url in PhoneGap webview + * @return "" if ok, or error message. + */ + public String authorize(String url) { + + this.mFb = new Facebook(APP_ID); + this.mFb.authorize((Activity) this.ctx, new AuthorizeListener()); + return "string"; + } + + class AuthorizeListener implements DialogListener { + public void onComplete(Bundle values) { + // Handle a successful login + } + public void onFacebookError(FacebookError e) { + e.printStackTrace(); + } + + public void onError(DialogError e) { + e.printStackTrace(); + } + + public void onCancel() { + } + } + + +} diff --git a/Android/Facebook/www/facebook.js b/Android/Facebook/www/facebook.js new file mode 100755 index 00000000..426ec4ea --- /dev/null +++ b/Android/Facebook/www/facebook.js @@ -0,0 +1,34 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010, IBM Corporation + */ + + +(function(){ + /** + * Constructor + */ + function Facebook() {} + + /** + * Display a new browser with the specified URL. + * + * @param url The url to load + * @param usePhoneGap Load url in PhoneGap webview [optional] + */ + Facebook.prototype.authorize = function(url) { + PhoneGap.exec(null, null, "FacebookAuth", "authorize", [url]); + }; + + /** + * Load ChildBrowser + */ + PhoneGap.addConstructor(function() { + PhoneGap.addPlugin("facebook", new Facebook()); + PluginManager.addService("FacebookAuth", "com.phonegap.plugins.facebook.FacebookAuth"); + }); + +})(); \ No newline at end of file diff --git a/Android/ShopGap/README.md b/Android/ShopGap/README.md new file mode 100644 index 00000000..8e3f7983 --- /dev/null +++ b/Android/ShopGap/README.md @@ -0,0 +1,100 @@ +# ShopGap plugin for Android/Phonegap + +Chris Saunders // @csaunders + +## About + +ShopGap is a wrapper around [Shopify4J](http://github.com/shopify/Shopify4J) to allow you to make Authenticated API calls to the Shopify API. + +## Dependencies + +You will need to include Shopify4J and all of it's dependencies in your project in order for this tool to work. This should be as simple as adding Shopify4J as a Library in your Android configuration. + +## Using the plugin + +**This has been developed against PhoneGap 1.1.0** + +* Add java code to your projects source + +* Register the plugin in the plugins.xml file + +```xml + +``` + +* Setup your authenticated session with the Shopify API + +```javascript +window.plugins.shopGap.setup( + 'YOUR_API_KEY', + 'GENERATED_API_PASSWORD', + 'SHOP_NAME', + successFunctionOrNull, + failureFunctionOrNull +); +``` + +* Make calls to the Shopify API + +```javascript +var success = function(resultJson){ + console.log(JSON.stringify(resultJson)); +} + +window.plugins.shopGap.read( + 'products', // endpoint + null, // query -- not supported yet + null, // data -- not needed for GET requests + function(r){success(r);}, // success callback + function(e){console.log}); // failure callback +``` + +### Endpoints + +The plugin takes care of most of the work for the endpoints, all you need to +do is fill in a few missing pieces. + +```javascript +// get all products, +window.plugins.shopGap.read('products', null, null, s, f); + +// get product 1 +window.plugins.shopGap.read('products/1', null, null, s, f); + +// create product +window.plugins.shopGap.create('products', null, JSON.stringify(newProduct), s, f); + +// update product 1 +window.plugins.shopGap.update('products/1', null, JSON.stringify(updateProduct), s, f); + +// delete product 1 +window.plugins.shopGap.destry('products/1', null, null, s, f); +``` + +## Release Notes + +0.1.0 Initial Release + +## License + +The MIT License + +Copyright (c) 2011 Chris Saunders + +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. \ No newline at end of file diff --git a/Android/ShopGap/ShopGapPlugin.java b/Android/ShopGap/ShopGapPlugin.java new file mode 100644 index 00000000..d4308d17 --- /dev/null +++ b/Android/ShopGap/ShopGapPlugin.java @@ -0,0 +1,138 @@ +package ca.christophersaunders.shopgap; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; +import com.phonegap.api.PluginResult.Status; +import com.shopify.api.client.ShopifyClient; +import com.shopify.api.credentials.Credential; +import com.shopify.api.endpoints.JsonPipeService; + +public class ShopGapPlugin extends Plugin { + private static final String API_KEY = "apikey"; + private static final String PASSWORD = "password"; + private static final String SHOPNAME = "shopname"; + private static final String CALL = "call"; + private static final String ENDPOINT = "endpoint"; + private static final String QUERY = "query"; + private static final String DATA = "data"; + + private ShopifyClient client; + private JsonPipeService service; + + enum Methods { CALL_API, SETUP }; + enum Call { READ, CREATE, UPDATE, DESTROY }; + + @Override + public PluginResult execute(String func, JSONArray arguments, String callbackId) { + try { + JSONObject argsMap = arguments.getJSONObject(0); + switch(determineMethod(func)) { + case CALL_API: + JSONObject results = callAPI(determineCall(argsMap.getString(CALL)), argsMap); + return new PluginResult(Status.OK, results); + case SETUP: + if(setupClient(argsMap)) { + return new PluginResult(Status.OK); + } + break; + default: + return new PluginResult(PluginResult.Status.INVALID_ACTION); + } + } catch (JSONException e) { + // Trololololololo + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + return null; + } + + private Methods determineMethod(String name){ + if(name.equals("callapi")) + return Methods.CALL_API; + if(name.equals("setup")) + return Methods.SETUP; + return null; + } + + private Call determineCall(String callname) { + if(callname.equals("read")) + return Call.READ; + if (callname.equals("update")) + return Call.UPDATE; + if (callname.equals("create")) + return Call.CREATE; + if (callname.equals("destroy")) + return Call.DESTROY; + return null; + } + + private boolean setupClient(JSONObject args) throws JSONException { + if (args.has(API_KEY) && args.has(PASSWORD) && args.has(SHOPNAME)) { + String apiKey = args.getString(API_KEY); + String passwd = args.getString(PASSWORD); + String shop = args.getString(SHOPNAME); + + Credential cred = new Credential(apiKey, "", shop, passwd); + client = new ShopifyClient(cred); + service = client.constructService(JsonPipeService.class); + return true; + } + return false; + } + + private JSONObject callAPI(Call call, JSONObject args) { + try { + String endpoint = null, data = null, query = null; + if(args.has(ENDPOINT)) + endpoint = args.getString(ENDPOINT); + if(args.has(QUERY)) + query = args.getString(QUERY); + if(args.has(DATA)) + data = args.getString(DATA); + + InputStream result = null; + + switch(call) { + case CREATE: + result = service.create(endpoint, data); + break; + case READ: + result = service.read(endpoint); + break; + case UPDATE: + result = service.update(endpoint, data); + break; + case DESTROY: + result = service.destroy(endpoint); + break; + } + + if( result != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BufferedInputStream bis = new BufferedInputStream(result); + byte[] resultData = new byte[0x4000]; + int dataRead = 0; + while((dataRead = bis.read(resultData)) > 0) { + baos.write(resultData, 0, dataRead); + } + return new JSONObject(new String(baos.toByteArray())); + } + + } catch (JSONException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return new JSONObject(); + } + +} diff --git a/Android/ShopGap/shopgap.js b/Android/ShopGap/shopgap.js new file mode 100644 index 00000000..4da4a332 --- /dev/null +++ b/Android/ShopGap/shopgap.js @@ -0,0 +1,41 @@ +var ShopGap = function(){}; + +ShopGap.prototype.setup = function(apiKey, password, shopname, onSuccessFn, onFailureFn){ + var args = {'apikey': apiKey, 'password': password, 'shopname': shopname}; + return PhoneGap.exec(onSuccessFn, onFailureFn, 'ShopGapPlugin', 'setup', [args]); +}; + +ShopGap.prototype.consArgs = function(call, endpoint, query, data){ + return {call: call, endpoint: endpoint, query: query, data: data}; +}; + +ShopGap.prototype.read = function(endpoint, query, data, success, failure){ + return this.callapi(this.consArgs('read', endpoint, query, data), success, failure); +}; + +ShopGap.prototype.update = function(endpoint, query, data, success, failure){ + return this.callapi(this.consArgs('update', endpoint, query, data), success, failure); +}; + +ShopGap.prototype.create = function(endpoint, query, data, success, failure){ + return this.callapi(this.consArgs('create', endpoint, query, data), success, failure); +}; + +ShopGap.prototype.destroy = function(endpoint, query, data, success, failure){ + return this.callapi(this.consArgs('destroy', endpoint, query, data), success, failure); +}; + +ShopGap.prototype.callapi = function(args, onSuccessFn, onFailureFn){ + return PhoneGap.exec(onSuccessFn, onFailureFn, 'ShopGapPlugin', 'callapi', [args]); +}; + +/* +ShopGap.prototype.callapi = function(call, endpoint, query, data, onSuccessFn, onFailureFn){ + var args = {'endpoint': endpoint, 'data': data, 'query': query}; + return PhoneGap.exec(onSuccessFn, onFailureFn, 'ShopGapPlugin', 'callapi', [args]); +}; +*/ + +PhoneGap.addConstructor(function(){ + PhoneGap.addPlugin('shopGap', new ShopGap()); +}); diff --git a/Android/Torch/README.md b/Android/Torch/README.md new file mode 100644 index 00000000..07e85657 --- /dev/null +++ b/Android/Torch/README.md @@ -0,0 +1,91 @@ +# Torch plugin for Phonegap (Android) # +By Arne de Bree + +## Adding the Plugin to your project ## +1. To install the plugin, move `Torch.js` to your project's www folder and include a reference to it +in your html files. + + <script src="Torch.js"></script> + +2. Create a folder called 'nl/debree/phonegap/plugin/torch' within your project's src folder. +3. And copy the java file into that new folder. + +
+    mkdir -p /src/nl/debree/phonegap/plugin/torch/
+    cp ./TorchPlugin.java /src/nl/debree/phonegap/plugin/torch/
+
+ +4. Add a plugin line to `res/xml/plugins.xml` + + <plugin name="Torch" value="nl.debree.phonegap.plugin.torch.TorchPlugin" /> + +## Using the plugin ## +The plugin creates the object `window.plugins.Torch` within your DOM. This object +exposes the following functions: + +- isOn +- isCapable +- toggle +- turnOn +- turnOff + +
+    window.plugins.Torch.isOn( 
+        function( result ) { console.log( "isOn: " + result.on ) }      // success
+    ,   function() { console.log( "error" ) }                           // error
+    );
+    
+    window.plugins.Torch.isCapable( 
+        function( result ) { console.log( "isCapable: " + result.capable ) }      // success
+    ,   function() { console.log( "error" ) }                           // error
+    );
+    
+    window.plugins.Torch.toggle( 
+        function() { console.log( "toggle" ) }                          // success
+    ,   function() { console.log( "error" ) }                           // error
+    );
+
+    window.plugins.Torch.turnOn( 
+        function() { console.log( "turnOn" ) }                          // success
+    ,   function() { console.log( "error" ) }                           // error
+    );
+
+    window.plugins.Torch.turnOff( 
+        function() { console.log( "turnOff" ) }                         // success
+    ,   function() { console.log( "error" ) }                           // error
+    );
+
+ + +## BUGS AND CONTRIBUTIONS ## +The latest bleeding-edge version is available [on GitHub](http://github.com/adebrees/phonegap-plugins/tree/master/Android/) +If you have a patch, fork my repo baby and send me a pull request. Submit bug reports on GitHub, please. + +## Licence ## + +The MIT License + +Copyright (c) 2011 Arne de Bree + +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. + + + + + \ No newline at end of file diff --git a/Android/Torch/Torch.js b/Android/Torch/Torch.js new file mode 100644 index 00000000..54c730b1 --- /dev/null +++ b/Android/Torch/Torch.js @@ -0,0 +1,61 @@ +/** + * Phonegap Torch plugin + * Copyright (c) Arne de Bree 2011 + * + */ + +/** + * + * @return Object literal singleton instance of Torch + */ +var Torch = function() {}; + +/** + * @param success The callback for success + * @param error The callback for error + */ +Torch.prototype.isCapable = function( success, error ) +{ + return PhoneGap.exec( success, error, "Torch", "isCapable", [] ); +}; + +/** + * @param success The callback for success + * @param error The callback for error + */ +Torch.prototype.isOn = function( success, error ) +{ + return PhoneGap.exec( success, error, "Torch", "isOn", [] ); +}; + +/** + * @param success The callback for success + * @param error The callback for error + */ +Torch.prototype.turnOn = function( success, error ) +{ + return PhoneGap.exec( success, error, "Torch", "turnOn", [] ); +}; + +/** + * @param success The callback for success + * @param error The callback for error + */ +Torch.prototype.turnOff = function( success, error ) +{ + return PhoneGap.exec( success, error, "Torch", "turnOff", [] ); +}; + +/** + * @param success The callback for success + * @param error The callback for error + */ +Torch.prototype.toggle = function( success, error ) +{ + return PhoneGap.exec( success, error, "Torch", "toggle", [] ); +}; + +PhoneGap.addConstructor( function() +{ + PhoneGap.addPlugin( "Torch", new Torch() ); +} ); \ No newline at end of file diff --git a/Android/Torch/TorchPlugin.java b/Android/Torch/TorchPlugin.java new file mode 100644 index 00000000..09557dd7 --- /dev/null +++ b/Android/Torch/TorchPlugin.java @@ -0,0 +1,154 @@ +/** + * Phonegap Torch Plugin + * Copyright (c) Arne de Bree 2011 + * + */ +package nl.debree.phonegap.plugin.torch; + +import java.util.List; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; +import com.phonegap.api.PluginResult.Status; + +import android.hardware.Camera; +import android.util.Log; + +/** + * Plugin to turn on or off the Camera Flashlight of an Android device + * after the capability is tested + */ +public class TorchPlugin extends Plugin { + + public static final String CMD_ON = "turnOn"; + public static final String CMD_OFF = "turnOff"; + public static final String CMD_TOGGLE = "toggle"; + public static final String CMD_IS_ON = "isOn"; + public static final String CMD_HAS_TORCH = "isCapable"; + + // Create camera and parameter objects + private Camera mCamera; + private Camera.Parameters mParameters; + private boolean mbTorchEnabled = false; + + /** + * Constructor + */ + public TorchPlugin() { + Log.d( "TorchPlugin", "Plugin created" ); + + mCamera = Camera.open(); + } + + /* + * Executes the request and returns PluginResult. + * + * @param action action to perform. Allowed values: turnOn, turnOff, toggle, isOn, isCapable + * @param data input data, currently not in use + * @param callbackId The callback id used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + * + * @see com.phonegap.api.Plugin#execute(java.lang.String, + * org.json.JSONArray, java.lang.String) + */ + @Override + public PluginResult execute(String action, JSONArray data, String callbackId) { + Log.d( "TorchPlugin", "Plugin Called " + action ); + + PluginResult result = null; + JSONObject response = new JSONObject(); + + if (action.equals(CMD_ON)) { + + this.toggleTorch( true ); + result = new PluginResult( Status.OK ); + + } else if (action.equals(CMD_OFF)) { + + this.toggleTorch( false ); + result = new PluginResult( Status.OK ); + + } else if (action.equals(CMD_TOGGLE)) { + + this.toggleTorch(); + result = new PluginResult( Status.OK ); + + } else if (action.equals(CMD_IS_ON)) { + try { + response.put( "on", mbTorchEnabled ); + + result = new PluginResult( Status.OK, response ); + } catch( JSONException jsonEx ) { + result = new PluginResult(Status.JSON_EXCEPTION); + } + } else if (action.equals(CMD_HAS_TORCH)) { + try { + response.put( "capable", this.isCapable() ); + + result = new PluginResult( Status.OK, response ); + } catch( JSONException jsonEx ) { + result = new PluginResult(Status.JSON_EXCEPTION); + } + + } else { + result = new PluginResult(Status.INVALID_ACTION); + Log.d( "TorchPlugin", "Invalid action : " + action + " passed"); + } + + return result; + } + + /** + * Test if this device has a Flashlight we can use and put in Torch mode + * + * @return boolean + */ + protected boolean isCapable() { + boolean result = false; + + List flashModes = mParameters.getSupportedFlashModes(); + + if (flashModes != null && flashModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) { + result = true; + } + + return result; + } + + /** + * True toggle function, turns the torch on when off and vise versa + * + */ + protected void toggleTorch() { + toggleTorch( !mbTorchEnabled ); + } + + /** + * Toggle the torch in the requested state + * + * @param state The requested state + * + */ + protected void toggleTorch(boolean state) { + mParameters = mCamera.getParameters(); + + // Make sure that torch mode is supported + // + if ( this.isCapable() ) { + if (state) { + mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); + } else { + mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON); + } + + // Commit the camera parameters + // + mCamera.setParameters(mParameters); + + mbTorchEnabled = state; + } + } +} \ No newline at end of file diff --git a/Android/VideoPlayer/README.md b/Android/VideoPlayer/README.md new file mode 100755 index 00000000..9019c6b2 --- /dev/null +++ b/Android/VideoPlayer/README.md @@ -0,0 +1,118 @@ +# VideoPlayer plugin for Phonegap # + +The video player allows you to display videos from your PhoneGap application. + +This command fires an Intent to have your devices video player show the video. + +## Adding the Plugin to your project ## + +Using this plugin requires [Android PhoneGap](http://github.com/phonegap/phonegap-android). + +1. To install the plugin, move www/video to your project's www folder and include a reference to it in your html file after phonegap.js. + + <script type="text/javascript" charset="utf-8" src="phonegap.js"></script>
+ <script type="text/javascript" charset="utf-8" src="video.js"></script> + +2. Create a directory within your project called "src/com/phonegap/plugins/video" and move VideoPlayer.java into it. + +3. In your res/xml/plugins.xml file add the following line: + + <plugin name="VideoPlayer" value="com.phonegap.plugins.video.VideoPlayer"/> + +## Using the plugin ## + +The plugin creates the object `window.plugins.video`. To use, call the play() method: + +
+  /**
+	* Display an intent to play the video.
+    *
+    * @param url           The url to play
+    */
+  play(url)
+
+ +Sample use: + + window.plugins.videoPlayer.play("http://path.to.my/video.mp4"); + window.plugins.videoPlayer.play("file:///path/to/my/video.mp4"); + +Note: You cannot play a video from the assets folder on Android. + +## RELEASE NOTES ## + +### October 2, 2011 ### + +* Initial release + + +## BUGS AND CONTRIBUTIONS ## + + +## LICENSE ## + +PhoneGap is available under *either* the terms of the modified BSD license *or* the +MIT License (2008). As a recipient of PhonegGap, you may choose which +license to receive this code under (except as noted in per-module LICENSE +files). Some modules may not be the copyright of Nitobi. These +modules contain explicit declarations of copyright in both the LICENSE files in +the directories in which they reside and in the code itself. No external +contributions are allowed under licenses which are fundamentally incompatible +with the MIT or BSD licenses that PhoneGap is distributed under. + +The text of the MIT and BSD licenses is reproduced below. + +--- + +### The "New" BSD License + +Copyright (c) 2005-2011, Nitobi Software Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Phonegap/Nitobi nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +### The MIT License + +Copyright (c) <2011> + + 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. + \ No newline at end of file diff --git a/Android/VideoPlayer/src/com/phonegap/plugins/video/VideoPlayer.java b/Android/VideoPlayer/src/com/phonegap/plugins/video/VideoPlayer.java new file mode 100644 index 00000000..1bd7b4eb --- /dev/null +++ b/Android/VideoPlayer/src/com/phonegap/plugins/video/VideoPlayer.java @@ -0,0 +1,45 @@ +package com.phonegap.plugins.video; + +import java.io.File; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.content.Intent; +import android.net.Uri; + +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; + +public class VideoPlayer extends Plugin { + + @Override + public PluginResult execute(String action, JSONArray args, String callbackId) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + + try { + if (action.equals("playVideo")) { + playVideo(args.getString(0)); + } + else { + status = PluginResult.Status.INVALID_ACTION; + } + return new PluginResult(status, result); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + + private void playVideo(String url) { + // Create URI + Uri uri = Uri.parse(url); + // Display video player + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(uri, "video/*"); + + this.ctx.startActivity(intent); + } + +} diff --git a/Android/VideoPlayer/www/video.js b/Android/VideoPlayer/www/video.js new file mode 100644 index 00000000..3cd77277 --- /dev/null +++ b/Android/VideoPlayer/www/video.js @@ -0,0 +1,30 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2011, IBM Corporation + */ + +/** + * Constructor + */ +function VideoPlayer() { +}; + +/** + * Starts the video player intent + * + * @param url The url to play + */ +VideoPlayer.prototype.play = function(url) { + PhoneGap.exec(null, null, "VideoPlayer", "playVideo", [url]); +}; + + +/** + * Load VideoPlayer + */ +PhoneGap.addConstructor(function() { + PhoneGap.addPlugin("videoPlayer", new VideoPlayer()); +}); diff --git a/README b/README.md similarity index 75% rename from README rename to README.md index 7d54dca7..6cdfa3e8 100644 --- a/README +++ b/README.md @@ -1,10 +1,6 @@ -This code is completely dependent on the PhoneGap project, also hosted on -GitHub ( github.com/phonegap/phonegap ) - - - - +This code is completely dependent on the Apache Callback (formerly PhoneGap) project, hosted on GitHub ( github.com/callback ) +Plugins will (for the time being) reside in this repository as pulling them into the Callback project would require all plugin contributors to meet stricter code licensing requirements pursuant to Apache's guidelines. The MIT License diff --git a/WindowsPhone/ChildBrowser/ChildBrowser.js b/WindowsPhone/ChildBrowser/ChildBrowser.js new file mode 100644 index 00000000..2834cac8 --- /dev/null +++ b/WindowsPhone/ChildBrowser/ChildBrowser.js @@ -0,0 +1,96 @@ +/* MIT licensed */ +// (c) 2010 Jesse MacFadyen, Nitobi + +/*global PhoneGap */ + +function ChildBrowser() { + // Does nothing +} + +// Callback when the location of the page changes +// called from native +ChildBrowser._onLocationChange = function(newLoc) +{ + window.plugins.childBrowser.onLocationChange(newLoc); +}; + +// Callback when the user chooses the 'Done' button +// called from native +ChildBrowser._onClose = function() +{ + window.plugins.childBrowser.onClose(); +}; + +// Callback when the user chooses the 'open in Safari' button +// called from native +ChildBrowser._onOpenExternal = function() +{ + window.plugins.childBrowser.onOpenExternal(); +}; + +// Pages loaded into the ChildBrowser can execute callback scripts, so be careful to +// check location, and make sure it is a location you trust. +// Warning ... don't exec arbitrary code, it's risky and could fuck up your app. +// called from native +ChildBrowser._onJSCallback = function(js,loc) +{ + // Not Implemented + //window.plugins.childBrowser.onJSCallback(js,loc); +}; + +/* The interface that you will use to access functionality */ + +// Show a webpage, will result in a callback to onLocationChange +ChildBrowser.prototype.showWebPage = function(loc,geolocationEnabled) +{ + var success = function(msg) + { + console.log("ChildBrowser.showWebPage success :: " + msg); + + var event = JSON.parse(msg); + + if (event.type == "locationChanged") { + ChildBrowser._onLocationChange(event.location); + } + }; + + var error = function(e) + { + console.log("ChildBrowser.showWebPage error :: " + e); + }; + + var options = + { + url:loc, + geolocationEnabled:(geolocationEnabled == true) + + }; + + PhoneGap.exec(success,error,"ChildBrowserCommand","showWebPage", options); + //setTimeout(this.close,5000); +}; + +// close the browser, will NOT result in close callback +ChildBrowser.prototype.close = function() +{ + PhoneGap.exec(null,null,"ChildBrowserCommand","close"); +}; + +// Not Implemented +ChildBrowser.prototype.jsExec = function(jsString) +{ + // Not Implemented!! + //PhoneGap.exec("ChildBrowserCommand.jsExec",jsString); +}; + +// Note: this plugin does NOT install itself, call this method some time after deviceready to install it +// it will be returned, and also available globally from window.plugins.childBrowser +ChildBrowser.install = function() +{ + if(!window.plugins) { + window.plugins = {}; + } + + window.plugins.childBrowser = new ChildBrowser(); + return window.plugins.childBrowser; +}; diff --git a/WindowsPhone/ChildBrowser/ChildBrowserCommand.cs b/WindowsPhone/ChildBrowser/ChildBrowserCommand.cs new file mode 100644 index 00000000..90103b0e --- /dev/null +++ b/WindowsPhone/ChildBrowser/ChildBrowserCommand.cs @@ -0,0 +1,198 @@ +using System; +using System.Net; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; +using Microsoft.Phone.Controls; +using System.Diagnostics; +using System.Runtime.Serialization; +using WP7GapClassLib.PhoneGap.UI; +using Microsoft.Phone.Shell; + +namespace WP7GapClassLib.PhoneGap.Commands +{ + [DataContract] + public class BrowserOptions + { + [DataMember] + public string url; + + [DataMember] + public bool isGeolocationEnabled; + } + + public class ChildBrowserCommand : BaseCommand + { + + private static WebBrowser browser; + private static ApplicationBarIconButton backButton; + private static ApplicationBarIconButton fwdButton; + + // Display an inderminate progress indicator + public void showWebPage(string options) + { + BrowserOptions opts = JSON.JsonHelper.Deserialize(options); + + Uri loc = new Uri(opts.url); + + Deployment.Current.Dispatcher.BeginInvoke(() => + { + if (browser != null) + { + browser.IsGeolocationEnabled = opts.isGeolocationEnabled; + browser.Navigate(loc); + } + else + { + PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; + if (frame != null) + { + PhoneApplicationPage page = frame.Content as PhoneApplicationPage; + if (page != null) + { + Grid grid = page.FindName("LayoutRoot") as Grid; + if (grid != null) + { + browser = new WebBrowser(); + browser.Navigate(loc); + + browser.LoadCompleted += new System.Windows.Navigation.LoadCompletedEventHandler(browser_LoadCompleted); + + browser.Navigating += new EventHandler(browser_Navigating); + browser.NavigationFailed += new System.Windows.Navigation.NavigationFailedEventHandler(browser_NavigationFailed); + browser.Navigated += new EventHandler(browser_Navigated); + browser.IsScriptEnabled = true; + browser.IsGeolocationEnabled = opts.isGeolocationEnabled; + grid.Children.Add(browser); + } + + ApplicationBar bar = new ApplicationBar(); + bar.BackgroundColor = Colors.Black; + bar.IsMenuEnabled = false; + + backButton = new ApplicationBarIconButton(); + backButton.Text = "Back"; + backButton.IconUri = new Uri("/Images/appbar.back.rest.png", UriKind.Relative); + backButton.Click += new EventHandler(backButton_Click); + backButton.IsEnabled = false; + bar.Buttons.Add(backButton); + + + fwdButton = new ApplicationBarIconButton(); + fwdButton.Text = "Forward"; + fwdButton.IconUri = new Uri("/Images/appbar.next.rest.png", UriKind.Relative); + fwdButton.Click += new EventHandler(fwdButton_Click); + fwdButton.IsEnabled = false; + bar.Buttons.Add(fwdButton); + + ApplicationBarIconButton closeBtn = new ApplicationBarIconButton(); + closeBtn.Text = "Close"; + closeBtn.IconUri = new Uri("/Images/appbar.close.rest.png", UriKind.Relative); + closeBtn.Click += new EventHandler(closeBtn_Click); + bar.Buttons.Add(closeBtn); + + page.ApplicationBar = bar; + } + + } + } + }); + } + + void browser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e) + { + + } + + void fwdButton_Click(object sender, EventArgs e) + { + if (browser != null) + { + try + { + browser.InvokeScript("execScript", "history.forward();"); + } + catch(Exception) + { + + } + } + } + + void backButton_Click(object sender, EventArgs e) + { + if (browser != null) + { + try + { + browser.InvokeScript("execScript", "history.back();"); + } + catch (Exception) + { + + } + } + } + + void closeBtn_Click(object sender, EventArgs e) + { + this.close(); + } + + + public void close(string options="") + { + if (browser != null) + { + Deployment.Current.Dispatcher.BeginInvoke(() => + { + PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; + if (frame != null) + { + PhoneApplicationPage page = frame.Content as PhoneApplicationPage; + if (page != null) + { + Grid grid = page.FindName("LayoutRoot") as Grid; + if (grid != null) + { + grid.Children.Remove(browser); + } + page.ApplicationBar = null; + } + } + browser = null; + }); + } + } + + void browser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e) + { + string message = "{\"type\":\"locationChanged\", \"location\":\"" + e.Uri.AbsoluteUri + "\"}"; + PluginResult result = new PluginResult(PluginResult.Status.OK, message); + result.KeepCallback = true; + this.DispatchCommandResult(result); + } + + void browser_NavigationFailed(object sender, System.Windows.Navigation.NavigationFailedEventArgs e) + { + string message = "{\"type\":\"navigationError\",\"location\":\"" + e.Uri.AbsoluteUri + "\"}"; + PluginResult result = new PluginResult(PluginResult.Status.ERROR, message); + result.KeepCallback = true; + this.DispatchCommandResult(result); + } + + void browser_Navigating(object sender, NavigatingEventArgs e) + { + string message = "{\"type\":\"locationAboutToChange\",\"location\":\"" + e.Uri.AbsoluteUri + "\"}"; + PluginResult result = new PluginResult(PluginResult.Status.OK, message); + result.KeepCallback = true; + this.DispatchCommandResult(result); + } + + } +} diff --git a/WindowsPhone/Facebook/FBConnect.js b/WindowsPhone/Facebook/FBConnect.js new file mode 100644 index 00000000..fb5386c0 --- /dev/null +++ b/WindowsPhone/Facebook/FBConnect.js @@ -0,0 +1,129 @@ +/* MIT licensed */ +// (c) 2010 Jesse MacFadyen, Nitobi +// (c) 2011 Sergey Grebnov +// Contributions, advice from : +// http://www.pushittolive.com/post/1239874936/facebook-login-on-iphone-phonegap + +/** +* FBConnect implements user authentication logic and session information store +*/ + +function FBConnect(client_id, redirect_uri, display) { + + this.client_id = client_id; + this.redirect_uri = redirect_uri; + this.display = display; + + this.resetSession(); + + if (window.plugins.childBrowser == null) { + ChildBrowser.install(); + } + +} + +/** +* User login +*/ +FBConnect.prototype.connect = function (scope) { + + var authorize_url = "https://graph.facebook.com/oauth/authorize?"; + authorize_url += "client_id=" + this.client_id; + authorize_url += "&redirect_uri=" + this.redirect_uri; + authorize_url += "&display=" + (this.display ? this.display : "touch"); + authorize_url += "&type=user_agent"; + + // extended permissions http://developers.facebook.com/docs/reference/api/permissions/ + if (scope) { + authorize_url += "&scope=" + scope; + } + + window.plugins.childBrowser.showWebPage(authorize_url); + var self = this; + window.plugins.childBrowser.onLocationChange = function (loc) { self.onLoginLocationChange(loc); }; +} + +FBConnect.prototype.onLoginLocationChange = function (newLoc) { + if (newLoc.indexOf(this.redirect_uri) == 0) { + var result = unescape(newLoc).split("#")[1]; + result = unescape(result); + + // TODO: Error Check + this.session.access_token = result.split("&")[0].split("=")[1]; + var expiresIn = parseInt(result.split("&")[1].split("=")[1]); + this.session.expires = new Date().valueOf() + expiresIn * 1000; + this.status = "connected"; + + window.plugins.childBrowser.close(); + this.onConnect(this); + + } +} + +/** +* User logout +*/ +FBConnect.prototype.logout = function () { + var authorize_url = "https://www.facebook.com/logout.php?"; + authorize_url += "&next=" + this.redirect_uri; + authorize_url += "&access_token=" + this.session.access_token; + console.log("logout url: " + authorize_url); + window.plugins.childBrowser.showWebPage(authorize_url); + var self = this; + window.plugins.childBrowser.onLocationChange = function (loc) { + console.log("onLogout"); + window.plugins.childBrowser.close(); + self.resetSession(); + self.status = "notConnected"; + self.onDisconnect(this); + }; +} + +/** +* Example method - returns your friends +*/ +FBConnect.prototype.getFriends = function () { + var url = "https://graph.facebook.com/me/friends?access_token=" + this.session.access_token; + var req = new XMLHttpRequest(); + + req.open("get", url, true); + req.send(null); + req.onerror = function () { alert("Error"); }; + return req; +} + +// Note: this plugin does NOT install itself, call this method some time after deviceready to install it +// it will be returned, and also available globally from window.plugins.fbConnect +FBConnect.install = function (client_id, redirect_uri, display) { + if (!window.plugins) { + window.plugins = {}; + } + window.plugins.fbConnect = new FBConnect(client_id, redirect_uri, display); + + return window.plugins.fbConnect; +} + +/** +* Session management functionality +*/ +FBConnect.prototype.resetSession = function () { + this.status = "unknown"; + this.session = {}; + this.session.access_token = null; + this.session.expires = 0; + this.session.secret = null; + this.session.session_key = null; + this.session.sig = null; + this.session.uid = null; +} + +FBConnect.prototype.restoreLastSession = function () { + var session = JSON.parse(localStorage.getItem('pg_fb_session')); + if (session) { + this.session = session; + } +} + +FBConnect.prototype.saveSession = function () { + localStorage.setItem('pg_fb_session', JSON.stringify(this.session)); +} diff --git a/WindowsPhone/Facebook/facebook.html b/WindowsPhone/Facebook/facebook.html new file mode 100644 index 00000000..fcd01920 --- /dev/null +++ b/WindowsPhone/Facebook/facebook.html @@ -0,0 +1,169 @@ + + + + + + + + + FBConnectBrowser + + + + + + + + + + + + + + + +

Loading ...

+ +
    + +
+ + + + diff --git a/WindowsPhone/LiveTiles/LiveTiles.cs b/WindowsPhone/LiveTiles/LiveTiles.cs new file mode 100644 index 00000000..e99ea82a --- /dev/null +++ b/WindowsPhone/LiveTiles/LiveTiles.cs @@ -0,0 +1,285 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2011, Nitobi Software Inc. + * Copyright (c) 2011, Microsoft Corporation + */ + +using System.Runtime.Serialization; +using WP7GapClassLib.PhoneGap; +using WP7GapClassLib.PhoneGap.Commands; +using WP7GapClassLib.PhoneGap.JSON; +using Microsoft.Phone.Shell; +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Phone.Controls; +using System.Windows; + +namespace PhoneGap.Extension.Commands +{ + /// + /// Implementes access to application live tiles + /// http://msdn.microsoft.com/en-us/library/hh202948(v=VS.92).aspx + /// + public class LiveTiles : BaseCommand + { + + #region Live tiles options + + /// + /// Represents LiveTile options + /// + [DataContract] + public class LiveTilesOptions + { + /// + /// Tile title + /// + [DataMember(IsRequired=false, Name="title")] + public string Title { get; set; } + + /// + /// Tile count + /// + [DataMember(IsRequired = false, Name = "count")] + public int Count { get; set; } + + /// + /// Tile image + /// + [DataMember(IsRequired = false, Name = "image")] + public string Image { get; set; } + + /// + /// Back tile title + /// + [DataMember(IsRequired = false, Name = "backTitle")] + public string BackTitle { get; set; } + + /// + /// Back tile content + /// + [DataMember(IsRequired = false, Name = "backContent")] + public string BackContent { get; set; } + + /// + /// Back tile image + /// + [DataMember(IsRequired = false, Name = "backImage")] + public string BackImage { get; set; } + + /// + /// Identifier for second tile + /// + [DataMember(IsRequired = false, Name = "secondaryTileUri")] + public string SecondaryTileUri { get; set; } + + } + #endregion + + /// + /// Updates application live tile + /// + public void updateAppTile(string options) + { + LiveTilesOptions liveTileOptions; + try + { + liveTileOptions = WP7GapClassLib.PhoneGap.JSON.JsonHelper.Deserialize(options); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); + return; + } + + try + { + ShellTile appTile = ShellTile.ActiveTiles.First(); + + if (appTile != null) + { + StandardTileData standardTile = CreateTileData(liveTileOptions); + appTile.Update(standardTile); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Can't get application tile")); + } + } + catch(Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error updating application tile")); + } + } + + /// + /// Creates secondary tile + /// + public void createSecondaryTile(string options) + { + LiveTilesOptions liveTileOptions; + try + { + liveTileOptions = WP7GapClassLib.PhoneGap.JSON.JsonHelper.Deserialize(options); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); + return; + } + + if (string.IsNullOrEmpty(liveTileOptions.Title) || string.IsNullOrEmpty(liveTileOptions.Image) || string.IsNullOrEmpty(liveTileOptions.SecondaryTileUri)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); + return; + } + try + { + ShellTile foundTile = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains(liveTileOptions.SecondaryTileUri)); + if (foundTile == null) + { + StandardTileData secondaryTile = CreateTileData(liveTileOptions); + PhoneApplicationPage currentPage; + Deployment.Current.Dispatcher.BeginInvoke(() => + { + currentPage = ((PhoneApplicationFrame)Application.Current.RootVisual).Content as PhoneApplicationPage; + string currentUri = currentPage.NavigationService.Source.ToString().Split('?')[0]; + ShellTile.Create(new Uri(currentUri + "?Uri=" + liveTileOptions.SecondaryTileUri, UriKind.Relative), secondaryTile); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); + }); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR,"Tile already exist")); + } + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR,"Error creating secondary live tile")); + } + } + + /// + /// Updates secondary tile + /// + public void updateSecondaryTile(string options) + { + LiveTilesOptions liveTileOptions; + try + { + liveTileOptions = WP7GapClassLib.PhoneGap.JSON.JsonHelper.Deserialize(options); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); + return; + } + + if (string.IsNullOrEmpty(liveTileOptions.SecondaryTileUri)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); + return; + } + + try + { + ShellTile foundTile = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains(liveTileOptions.SecondaryTileUri)); + + if (foundTile != null) + { + StandardTileData liveTile = this.CreateTileData(liveTileOptions); + foundTile.Update(liveTile); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Can't get secondary live tile")); + } + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR,"Error updating secondary live tile")); + } + } + + /// + /// Deletes secondary tile + /// + public void deleteSecondaryTile(string options) + { + LiveTilesOptions liveTileOptions; + try + { + liveTileOptions = WP7GapClassLib.PhoneGap.JSON.JsonHelper.Deserialize(options); + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); + return; + } + + if (string.IsNullOrEmpty(liveTileOptions.SecondaryTileUri)) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); + return; + } + try + { + ShellTile foundTile = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains(liveTileOptions.SecondaryTileUri)); + if (foundTile != null) + { + foundTile.Delete(); + DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Can't get secondary live tile")); + } + + } + catch (Exception e) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error deleting secondary live tile")); + } + } + + + /// + /// Cerates tile data + /// + private StandardTileData CreateTileData(LiveTilesOptions liveTileOptions) + { + StandardTileData standardTile = new StandardTileData(); + if (!string.IsNullOrEmpty(liveTileOptions.Title)) + { + standardTile.Title = liveTileOptions.Title; + } + if (!string.IsNullOrEmpty(liveTileOptions.Image)) + { + standardTile.BackgroundImage = new Uri(liveTileOptions.Image, UriKind.RelativeOrAbsolute); + } + if (liveTileOptions.Count > 0) + { + standardTile.Count = liveTileOptions.Count; + } + if (!string.IsNullOrEmpty(liveTileOptions.BackTitle)) + { + standardTile.BackTitle = liveTileOptions.BackTitle; + } + if (!string.IsNullOrEmpty(liveTileOptions.BackContent)) + { + standardTile.BackContent = liveTileOptions.BackContent; + } + if (!string.IsNullOrEmpty(liveTileOptions.BackImage)) + { + standardTile.BackBackgroundImage = new Uri(liveTileOptions.BackImage, UriKind.RelativeOrAbsolute); + } + return standardTile; + } + + } +} \ No newline at end of file diff --git a/WindowsPhone/LiveTiles/README.md b/WindowsPhone/LiveTiles/README.md new file mode 100644 index 00000000..b6fe557c --- /dev/null +++ b/WindowsPhone/LiveTiles/README.md @@ -0,0 +1,25 @@ +Live Tiles plugin usage: +=============== +Source files +--- +liveTiles.js - plugin definition and js implementation +LiveTiles.cs - native side implementation +liveTilesExample.html- usage example + +In your head +--- + +[script type="text/javascript" charset="utf-8" src="liveTiles.js"][/script] + + +Somewhere in your code +--- + + + navigator.plugins.liveTiles.updateAppTile(success, fail,{title: 'title', image:'Images/appbar.next.rest.png', count: 5, backTitle: 'Back title', backContent:'Back side', backImage : 'Images/appbar.close.rest.png'}); + + navigator.plugins.liveTiles.createSecondaryTile(success, fail, { title: 'title', image: 'Images/appbar.save.rest.png', count: 5, secondaryTileUri: 'www/myPage.html',backTitle:'back'}); + + navigator.plugins.liveTiles.updateSecondaryTile(success, fail, { title: 'title', count: 5, secondaryTileUri: 'www/myPage.html' }); + + navigator.plugins.liveTiles.deleteSecondaryTile(success, fail, { secondaryTileUri: 'www/myPage.html' }); \ No newline at end of file diff --git a/WindowsPhone/LiveTiles/liveTiles.js b/WindowsPhone/LiveTiles/liveTiles.js new file mode 100644 index 00000000..8d4895e4 --- /dev/null +++ b/WindowsPhone/LiveTiles/liveTiles.js @@ -0,0 +1,55 @@ +PhoneGap.addConstructor(function () { + + navigator.plugins.liveTiles = + { + updateAppTile: function (successCallback, errorCallback, options) { + if (successCallback && (typeof successCallback !== "function")) { + console.log("LiveTiles Error: successCallback is not a function"); + return; + } + + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("LiveTiles Error: errorCallback is not a function"); + return; + } + PhoneGap.exec(successCallback, errorCallback, "LiveTiles", "updateAppTile", options); + }, + + createSecondaryTile: function (successCallback, errorCallback, options) { + if (successCallback && (typeof successCallback !== "function")) { + console.log("LiveTiles Error: successCallback is not a function"); + return; + } + + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("LiveTiles Error: errorCallback is not a function"); + return; + } + PhoneGap.exec(successCallback, errorCallback, "LiveTiles", "createSecondaryTile", options); + }, + updateSecondaryTile: function (successCallback, errorCallback, options) { + if (successCallback && (typeof successCallback !== "function")) { + console.log("LiveTiles Error: successCallback is not a function"); + return; + } + + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("LiveTiles Error: errorCallback is not a function"); + return; + } + PhoneGap.exec(successCallback, errorCallback, "LiveTiles", "updateSecondaryTile", options); + }, + deleteSecondaryTile: function (successCallback, errorCallback, options) { + if (successCallback && (typeof successCallback !== "function")) { + console.log("LiveTile Error: successCallback is not a function"); + return; + } + + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("LiveTiles Error: errorCallback is not a function"); + return; + } + PhoneGap.exec(successCallback, errorCallback, "LiveTiles", "deleteSecondaryTile", options); + } + } +}); \ No newline at end of file diff --git a/WindowsPhone/LiveTiles/liveTilesExample.html b/WindowsPhone/LiveTiles/liveTilesExample.html new file mode 100644 index 00000000..bd2572d2 --- /dev/null +++ b/WindowsPhone/LiveTiles/liveTilesExample.html @@ -0,0 +1,131 @@ + + + + + + + PhoneGap + + + + + + + + + + + + +

Live tile

+
+ New title:
+ +
+ New count:
+ +
+ Result: +
+

Action

+ Update application tile + Create secondary tile
(will open this page)
+ Update secondary tile + Delete secondary tile +

 

Back + + \ No newline at end of file diff --git a/WindowsPhone/PGMapLauncher/PGMapLauncher.cs b/WindowsPhone/PGMapLauncher/PGMapLauncher.cs new file mode 100644 index 00000000..58837039 --- /dev/null +++ b/WindowsPhone/PGMapLauncher/PGMapLauncher.cs @@ -0,0 +1,124 @@ +using System; +using System.Net; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; +using Microsoft.Phone.Tasks; +using Microsoft.Phone.Controls; +using System.Device.Location; +using System.Runtime.Serialization; +using System.Diagnostics; + +namespace WP7GapClassLib.PhoneGap.Commands +{ + public class PGMapLauncher : PhoneGap.Commands.BaseCommand + { + + [DataContract] + public class SearchOptions + { + [DataMember(IsRequired = false, Name = "searchTerm")] + public string SearchTerm { get; set; } + + [DataMember(IsRequired = false, Name = "center")] + public Coordinates Center; + } + + [DataContract] + public class Coordinates + { + [DataMember(IsRequired = false, Name = "latitude")] + public double Latitude; + + [DataMember(IsRequired = false, Name = "longitude")] + public double Longitude; + } + + [DataContract] + public class LabeledCoordinates + { + [DataMember(IsRequired = false, Name = "coordinates")] + public Coordinates Coordinates; + + [DataMember(IsRequired = false, Name = "label")] + public string Label; + } + + [DataContract] + public class GetDirectionsOptions + { + [DataMember(IsRequired = false, Name = "startPosition")] + public LabeledCoordinates Start; + + [DataMember(IsRequired = true, Name = "endPosition")] + public LabeledCoordinates End; + } + + + public void searchNear(string options) + { + SearchOptions searchOptions = JSON.JsonHelper.Deserialize(options); + BingMapsTask bingMapsTask = new BingMapsTask(); + + //Omit the Center property to use the user's current location. + if (searchOptions.Center != null) + { + bingMapsTask.Center = new GeoCoordinate(searchOptions.Center.Latitude, searchOptions.Center.Longitude); + } + + if (searchOptions.SearchTerm != null) + { + + bingMapsTask.SearchTerm = searchOptions.SearchTerm; + bingMapsTask.Show(); + } + else + { + Debug.WriteLine("Error::searchTerm must be specified for map searching"); + } + + + } + + public void getDirections(string options) + { + GetDirectionsOptions directionOptions = JSON.JsonHelper.Deserialize(options); + + BingMapsDirectionsTask bingMapsDirectionsTask = new BingMapsDirectionsTask(); + + // You can specify a label and a geocoordinate for the end point. + if (directionOptions.Start != null) + { + LabeledMapLocation startLML = new LabeledMapLocation(); + startLML.Location = new GeoCoordinate(directionOptions.Start.Coordinates.Latitude, directionOptions.Start.Coordinates.Longitude); + if (directionOptions.Start.Label != null) + { + startLML.Label = directionOptions.Start.Label; + } + bingMapsDirectionsTask.Start = startLML; + } + // If you set the geocoordinate parameter to null, the label parameter is used as a search term. + if (directionOptions.End != null) + { + LabeledMapLocation endLML = new LabeledMapLocation(); + if (directionOptions.End.Coordinates != null) + { + endLML.Location = new GeoCoordinate(directionOptions.End.Coordinates.Latitude, directionOptions.End.Coordinates.Longitude); + } + if (directionOptions.End.Label != null) + { + endLML.Label = directionOptions.End.Label; + } + bingMapsDirectionsTask.End = endLML; + } + + // If bingMapsDirectionsTask.Start is not set, the user's current location is used as the start point. + bingMapsDirectionsTask.Show(); + } + } +} diff --git a/WindowsPhone/PGMapLauncher/PGMapLauncher.js b/WindowsPhone/PGMapLauncher/PGMapLauncher.js new file mode 100644 index 00000000..da946c23 --- /dev/null +++ b/WindowsPhone/PGMapLauncher/PGMapLauncher.js @@ -0,0 +1,31 @@ +PhoneGap.addConstructor(function() { + + var LabeledLocation = function(label,lat,lon) + { + this.label = label; + if(lat && lon) + { + this.coordinates = {"latitude":lat,"longitude":lon}; + } + }; + + navigator.plugins.pgMapLauncher = + { + // searchText is required. + // If nearToCoords is null, the map will search near to the current location + searchNear:function(searchTerm, nearToCoords ) + { + var options = {"searchTerm":searchTerm,"center":nearToCoords}; + PhoneGap.exec(null,null,"PGMapLauncher","searchNear",options); + }, + + // toLabeledLocation is required + // if toLabeledLocation. + // if fromLabeledLocation is null, the current location will be used + getDirections:function(toLabeledLocation,fromLabeledLocation) + { + PhoneGap.exec(null,null,"PGMapLauncher","getDirections", + {"startPosition":fromLabeledLocation,"endPosition":toLabeledLocation}); + } + } +}); \ No newline at end of file diff --git a/WindowsPhone/PGSocialShare/PGSocialShare.cs b/WindowsPhone/PGSocialShare/PGSocialShare.cs new file mode 100644 index 00000000..95facc02 --- /dev/null +++ b/WindowsPhone/PGSocialShare/PGSocialShare.cs @@ -0,0 +1,76 @@ +using System; +using System.Net; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; +using System.Runtime.Serialization; +using Microsoft.Phone.Tasks; + +namespace WP7GapClassLib.PhoneGap.Commands +{ + + public class PGSocialShare : BaseCommand + { + public enum ShareType + { + Status = 0, // status update to twitter/facebook/... all supported device accounts + Link // share a link on twitter/facebook/ ... all supported device accounts + } + + + [DataContract] + public class ShareOptions + { + [DataMember] + public string url; + + [DataMember] + public string title; + + [DataMember] + public string message; + + [DataMember] + public ShareType shareType = ShareType.Status; // default + } + + public void share(string options) + { + ShareOptions opts = JSON.JsonHelper.Deserialize(options); + switch (opts.shareType) + { + case ShareType.Status : + shareStatus(opts.message); + break; + case ShareType.Link : + shareLink(opts.title, opts.url, opts.message); + break; + } + } + + protected void shareStatus(string msg) + { + ShareStatusTask shareStatusTask = new ShareStatusTask(); + shareStatusTask.Status = msg; + shareStatusTask.Show(); + + this.DispatchCommandResult(); + } + + protected void shareLink(string title, string url, string msg) + { + ShareLinkTask shareLinkTask = new ShareLinkTask(); + shareLinkTask.Title = title; + shareLinkTask.LinkUri = new Uri(url, UriKind.Absolute); + shareLinkTask.Message = msg; + shareLinkTask.Show(); + + this.DispatchCommandResult(); + } + } +} diff --git a/WindowsPhone/PGSocialShare/PGSocialShare.js b/WindowsPhone/PGSocialShare/PGSocialShare.js new file mode 100644 index 00000000..80f3b8bf --- /dev/null +++ b/WindowsPhone/PGSocialShare/PGSocialShare.js @@ -0,0 +1,44 @@ +/* MIT licensed */ +// (c) 2011 Jesse MacFadyen, Adobe Systems Incorporated + + + +(function(){ + + var PGSocialShare = + { + ShareType: + { + status:0, + link:1 + } + } + + PhoneGap.addConstructor(function() { + + navigator.plugins.pgSocialShare = + { + shareStatus:function(msg) + { + var options = {"message":msg,"shareType":PGSocialShare.ShareType.status}; + PhoneGap.exec(null,null,"PGSocialShare","share",options); + }, + + shareLink:function(title,url,msg) + { + var options = {"message":msg, + "title":title, + "url":url, + "shareType":PGSocialShare.ShareType.link}; + + PhoneGap.exec(null,null,"PGSocialShare","share",options); + } + } + + + + }); + + + +})(); \ No newline at end of file diff --git a/WindowsPhone/PGSocialShare/readme.md b/WindowsPhone/PGSocialShare/readme.md new file mode 100644 index 00000000..883b1fd3 --- /dev/null +++ b/WindowsPhone/PGSocialShare/readme.md @@ -0,0 +1,30 @@ +Sample Use: +=============== + +In your head +--- + +[script type="text/javascript" charset="utf-8" src="PGSocialShare.js"][/script] + + +Somewhere in your code +--- + + function shareStatus() + { + navigator.plugins.pgSocialShare.shareStatus("This was shared from JS+PhoneGap+WP7 Yo!"); + } + + function shareLink() + { + navigator.plugins.pgSocialShare.shareLink("WP7 PhoneGap Plugins", + "https://github.com/purplecabbage/phonegap-plugins/tree/master/WindowsPhone", + "Watch for updates here soon! Shared from JavaScript"); + } + + +In your markup : +--- + + + diff --git a/iPhone/BarcodeScanner/barcodescanner.js b/iPhone/BarcodeScanner/barcodescanner.js index 8d48d69d..fe10611f 100644 --- a/iPhone/BarcodeScanner/barcodescanner.js +++ b/iPhone/BarcodeScanner/barcodescanner.js @@ -7,6 +7,9 @@ var BarcodeScanner = function() { } +BarcodeScanner.prototype.callbackMap = {}; +BarcodeScanner.prototype.callbackIdx = 0; + /* That's your lot for the moment */ BarcodeScanner.Type = { @@ -16,12 +19,27 @@ BarcodeScanner.Type = { /* Types are ignored at the moment until I implement any other than QR Code */ BarcodeScanner.prototype.scan = function(types, success, fail) { - return PhoneGap.exec("BarcodeScanner.scan", GetFunctionName(success), GetFunctionName(fail), types); + + var plugin = window.plugins.barcodeScanner, + cbMap = plugin.callbackMap, + key = 'scan' + plugin.callbackIdx++; + + cbMap[key] = { + success: function(result) { + delete cbMap[key]; + success(result); + }, + fail: function(result) { + delete cbMap[key]; + fail(result); + } + }; + + var cbPrefix = 'window.plugins.barcodeScanner.callbackMap.' + key; + + return PhoneGap.exec("BarcodeScanner.scan", cbPrefix + ".success", cbPrefix + ".fail", types); }; - - - PhoneGap.addConstructor(function() { if(!window.plugins) diff --git a/iPhone/ChildBrowser/ChildBrowserViewController.h b/iPhone/ChildBrowser/ChildBrowserViewController.h index 87676142..1d629ed0 100644 --- a/iPhone/ChildBrowser/ChildBrowserViewController.h +++ b/iPhone/ChildBrowser/ChildBrowserViewController.h @@ -31,14 +31,18 @@ IBOutlet UIBarButtonItem* backBtn; IBOutlet UIBarButtonItem* fwdBtn; IBOutlet UIBarButtonItem* safariBtn; + IBOutlet UIBarButtonItem* documentBtn; IBOutlet UIActivityIndicatorView* spinner; + UIDocumentInteractionController* docController; BOOL scaleEnabled; BOOL isImage; NSString* imageURL; NSArray* supportedOrientations; id delegate; + id docControllerDelegate; } +@property (retain) UIDocumentInteractionController* docController; @property (nonatomic, retain)id delegate; @property (nonatomic, retain) NSArray* supportedOrientations; @property(retain) NSString* imageURL; @@ -48,7 +52,9 @@ - (ChildBrowserViewController*)initWithScale:(BOOL)enabled; - (IBAction)onDoneButtonPress:(id)sender; - (IBAction)onSafariButtonPress:(id)sender; +- (IBAction)onDocumentButtonPress:(id)sender; +- (UIDocumentInteractionController *)setupControllerWithURL:(NSURL *)fileURL usingDelegate:(id )interactionDelegate; - (void)loadURL:(NSString*)url; --(void)closeBrowser; +- (void)closeBrowser; @end diff --git a/iPhone/ChildBrowser/ChildBrowserViewController.m b/iPhone/ChildBrowser/ChildBrowserViewController.m index 6fc9cce4..d6c97c85 100644 --- a/iPhone/ChildBrowser/ChildBrowserViewController.m +++ b/iPhone/ChildBrowser/ChildBrowserViewController.m @@ -14,6 +14,7 @@ @implementation ChildBrowserViewController @synthesize supportedOrientations; @synthesize isImage; @synthesize delegate; +@synthesize docController; /* // The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad. @@ -94,8 +95,9 @@ - (void)dealloc { [backBtn release]; [fwdBtn release]; [safariBtn release]; + [documentBtn release]; [spinner release]; - [ supportedOrientations release]; + [supportedOrientations release]; [super dealloc]; } @@ -107,7 +109,7 @@ -(void)closeBrowser [delegate onClose]; } - [ [super parentViewController] dismissModalViewControllerAnimated:YES]; + [super dismissModalViewControllerAnimated:YES]; } -(IBAction) onDoneButtonPress:(id)sender @@ -136,11 +138,52 @@ -(IBAction) onSafariButtonPress:(id)sender { NSURLRequest *request = webView.request; [[UIApplication sharedApplication] openURL:request.URL]; - } + } +} - + +- (IBAction)onDocumentButtonPress:(id)sender +{ + NSURLRequest *request = webView.request; + NSURL *url = request.URL; + + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *filePath = [NSString stringWithFormat:@"%@/%@", [paths objectAtIndex:0],@"child_browser_temp.pdf"]; + + NSData *urlData = [NSData dataWithContentsOfURL:url]; + [urlData writeToFile:filePath atomically:YES]; + + docController = [[self setupControllerWithURL:[NSURL fileURLWithPath:filePath] usingDelegate:docControllerDelegate] retain]; + + if (docController) + { + BOOL canOpen = [docController presentOpenInMenuFromBarButtonItem:documentBtn animated:NO]; + [docController dismissMenuAnimated:NO]; + + if (canOpen) + { + [docController presentOpenInMenuFromBarButtonItem:documentBtn animated:YES]; + } + else + { + UIAlertView *message = [[UIAlertView alloc] initWithTitle:@"Sorry!" message:@"No applications were found that support this filetype." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; + [message show]; + [message release]; + } + } } + +- (UIDocumentInteractionController *)setupControllerWithURL:(NSURL *)fileURL usingDelegate:(id )interactionDelegate { + + UIDocumentInteractionController *interactionController = + [UIDocumentInteractionController interactionControllerWithURL: fileURL]; + interactionController.delegate = interactionDelegate; + + return interactionController; +} + + - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation { BOOL autoRotate = [self.supportedOrientations count] > 1; // autorotate if only more than 1 orientation supported diff --git a/iPhone/ChildBrowser/ChildBrowserViewController.xib b/iPhone/ChildBrowser/ChildBrowserViewController.xib index cc8dd659..fb034046 100644 --- a/iPhone/ChildBrowser/ChildBrowserViewController.xib +++ b/iPhone/ChildBrowser/ChildBrowserViewController.xib @@ -2,16 +2,23 @@ 768 - 10K540 - 851 - 1038.36 - 461.00 + 11B26 + 1617 + 1138 + 566.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 141 + 534 - + YES + IBProxyObject + IBUIBarButtonItem + IBUILabel + IBUIToolbar + IBUIActivityIndicatorView + IBUIWebView + IBUIView YES @@ -22,9 +29,7 @@ YES - - YES - + YES @@ -37,16 +42,17 @@ IBCocoaTouchFramework - + 292 YES -2147483374 - {480, 229} + {{0, 71}, {480, 229}} - + + 1 MCAwIDAAA @@ -59,9 +65,10 @@ - 266 - {{0, 256}, {480, 44}} + 290 + {480, 44} + NO NO @@ -90,6 +97,10 @@ 5 + + NSImage + arrow_left.png + IBCocoaTouchFramework 32 @@ -100,6 +111,10 @@ 5 + + NSImage + arrow_right.png + IBCocoaTouchFramework 32 @@ -110,6 +125,10 @@ 5 + + NSImage + compass.png + IBCocoaTouchFramework 32 @@ -119,13 +138,19 @@ 5 + + IBCocoaTouchFramework + + 9 + - 270 - {{5, 230}, {418, 21}} + 294 + {{7, 46}, {418, 21}} + 4 @@ -153,8 +178,10 @@ -2147483383 - {{454, 231}, {20, 20}} + {{453, 47}, {20, 20}} + + NO NO NO @@ -162,7 +189,9 @@ {{0, 20}, {480, 300}} - + + + 3 MC41AA @@ -172,6 +201,7 @@ + 3 3 IBCocoaTouchFramework @@ -284,6 +314,22 @@ 40 + + + documentBtn + + + + 43 + + + + onDocumentButtonPress: + + + + 44 + @@ -337,6 +383,7 @@ + @@ -404,6 +451,14 @@ + + 42 + + + YES + + + @@ -411,24 +466,22 @@ YES -1.CustomClassName + -1.IBPluginDependency -2.CustomClassName - 1.IBEditorWindowLastContentRect + -2.IBPluginDependency 1.IBPluginDependency 10.IBPluginDependency 11.IBPluginDependency 13.IBPluginDependency - 13.IBViewBoundsToFrameTransform 14.IBPluginDependency 15.IBPluginDependency 32.IBPluginDependency - 32.IBViewBoundsToFrameTransform 37.IBPluginDependency 38.IBPluginDependency 39.IBPluginDependency 4.IBPluginDependency - 4.IBViewBoundsToFrameTransform + 42.IBPluginDependency 6.IBPluginDependency - 6.IBViewBoundsToFrameTransform 7.IBPluginDependency 8.IBPluginDependency 9.IBPluginDependency @@ -436,32 +489,22 @@ YES ChildBrowserViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin UIResponder - {{250, 643}, {480, 320}} com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - P4AAAL+AAABCoAAAwygAAA - com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - P4AAAL+AAABD5gAAw3kAAA - com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - P4AAAL+AAABB8AAAwwUAAA - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - P4AAAL+AAAAAAAAAw10AAA - + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin @@ -470,20 +513,16 @@ YES - - YES - + YES - - YES - + - 40 + 44 @@ -495,6 +534,7 @@ YES YES + onDocumentButtonPress: onDoneButtonPress: onSafariButtonPress: @@ -502,17 +542,23 @@ YES id id + id YES YES + onDocumentButtonPress: onDoneButtonPress: onSafariButtonPress: YES + + onDocumentButtonPress: + id + onDoneButtonPress: id @@ -530,7 +576,7 @@ addressLabel backBtn closeBtn - delegate + documentBtn fwdBtn refreshBtn safariBtn @@ -542,7 +588,7 @@ UILabel UIBarButtonItem UIBarButtonItem - id + UIBarButtonItem UIBarButtonItem UIBarButtonItem UIBarButtonItem @@ -557,7 +603,7 @@ addressLabel backBtn closeBtn - delegate + documentBtn fwdBtn refreshBtn safariBtn @@ -579,8 +625,8 @@ UIBarButtonItem - delegate - id + documentBtn + UIBarButtonItem fwdBtn @@ -606,249 +652,7 @@ IBProjectSource - Plugins/ChildBrowser/ChildBrowserViewController.h - - - - - YES - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSError.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSFileManager.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSKeyValueCoding.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSKeyValueObserving.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSKeyedArchiver.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSObject.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSRunLoop.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSThread.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSURL.h - - - - NSObject - - IBFrameworkSource - Foundation.framework/Headers/NSURLConnection.h - - - - NSObject - - IBFrameworkSource - QuartzCore.framework/Headers/CAAnimation.h - - - - NSObject - - IBFrameworkSource - QuartzCore.framework/Headers/CALayer.h - - - - NSObject - - IBFrameworkSource - UIKit.framework/Headers/UIAccessibility.h - - - - NSObject - - IBFrameworkSource - UIKit.framework/Headers/UINibLoading.h - - - - NSObject - - IBFrameworkSource - UIKit.framework/Headers/UIResponder.h - - - - UIActivityIndicatorView - UIView - - IBFrameworkSource - UIKit.framework/Headers/UIActivityIndicatorView.h - - - - UIBarButtonItem - UIBarItem - - IBFrameworkSource - UIKit.framework/Headers/UIBarButtonItem.h - - - - UIBarItem - NSObject - - IBFrameworkSource - UIKit.framework/Headers/UIBarItem.h - - - - UILabel - UIView - - IBFrameworkSource - UIKit.framework/Headers/UILabel.h - - - - UIResponder - NSObject - - - - UISearchBar - UIView - - IBFrameworkSource - UIKit.framework/Headers/UISearchBar.h - - - - UISearchDisplayController - NSObject - - IBFrameworkSource - UIKit.framework/Headers/UISearchDisplayController.h - - - - UIToolbar - UIView - - IBFrameworkSource - UIKit.framework/Headers/UIToolbar.h - - - - UIView - - IBFrameworkSource - UIKit.framework/Headers/UIPrintFormatter.h - - - - UIView - - IBFrameworkSource - UIKit.framework/Headers/UITextField.h - - - - UIView - UIResponder - - IBFrameworkSource - UIKit.framework/Headers/UIView.h - - - - UIViewController - - IBFrameworkSource - MediaPlayer.framework/Headers/MPMoviePlayerViewController.h - - - - UIViewController - - IBFrameworkSource - UIKit.framework/Headers/UINavigationController.h - - - - UIViewController - - IBFrameworkSource - UIKit.framework/Headers/UIPopoverController.h - - - - UIViewController - - IBFrameworkSource - UIKit.framework/Headers/UISplitViewController.h - - - - UIViewController - - IBFrameworkSource - UIKit.framework/Headers/UITabBarController.h - - - - UIViewController - UIResponder - - IBFrameworkSource - UIKit.framework/Headers/UIViewController.h - - - - UIWebView - UIView - - IBFrameworkSource - UIKit.framework/Headers/UIWebView.h + ./Classes/ChildBrowserViewController.h @@ -868,8 +672,22 @@ YES - 3 - 141 + + YES + + YES + arrow_left.png + arrow_right.png + compass.png + + + YES + {24, 24} + {24, 24} + {24, 24} + + + 534 diff --git a/iPhone/ClipboardPlugin/clipboardPlugin.js b/iPhone/ClipboardPlugin/clipboardPlugin.js index 1efc3e31..64abb8be 100644 --- a/iPhone/ClipboardPlugin/clipboardPlugin.js +++ b/iPhone/ClipboardPlugin/clipboardPlugin.js @@ -24,7 +24,7 @@ ClipboardPlugin.prototype.setText = function(text) */ ClipboardPlugin.prototype.getText = function(callback) { - PhoneGap.exec("ClipboardPlugin.getText", GetFunctionName(callback)); + PhoneGap.exec("ClipboardPlugin.getText", callback); } /** diff --git a/iPhone/EmailComposer/EmailComposer.js b/iPhone/EmailComposer/EmailComposer.js index 74ac59f5..2d504423 100644 --- a/iPhone/EmailComposer/EmailComposer.js +++ b/iPhone/EmailComposer/EmailComposer.js @@ -1,5 +1,5 @@ // window.plugins.emailComposer - + function EmailComposer() { this.resultCallback = null; // Function @@ -34,7 +34,7 @@ EmailComposer.prototype.showEmailComposer = function(subject,body,toRecipients,c if(bIsHTML) args.bIsHTML = bIsHTML; - PhoneGap.exec("EmailComposer.showEmailComposer",args); + PhoneGap.exec("com.phonegap.emailComposer.showEmailComposer",args); } // this will be forever known as the orch-func -jm @@ -58,4 +58,4 @@ PhoneGap.addConstructor(function() window.plugins = {}; } window.plugins.emailComposer = new EmailComposer(); -}); +}); \ No newline at end of file diff --git a/iPhone/GoogleAnalytics/GoogSDK/GANTracker.h b/iPhone/GoogleAnalytics/GoogSDK/GANTracker.h new file mode 100644 index 00000000..23ad52d0 --- /dev/null +++ b/iPhone/GoogleAnalytics/GoogSDK/GANTracker.h @@ -0,0 +1,96 @@ +// +// GANTracker.h +// Google Analytics iPhone SDK. +// Version: 1.1 +// +// Copyright 2009 Google Inc. All rights reserved. +// + +extern NSString* const kGANTrackerErrorDomain; +extern NSInteger const kGANTrackerNotStartedError; +extern NSInteger const kGANTrackerInvalidInputError; +extern NSInteger const kGANTrackerEventsPerSessionLimitError; +extern NSUInteger const kGANMaxCustomVariables; +extern NSUInteger const kGANMaxCustomVariableLength; +extern NSUInteger const kGANVisitorScope; +extern NSUInteger const kGANSessionScope; +extern NSUInteger const kGANPageScope; + +@protocol GANTrackerDelegate; +typedef struct __GANTrackerPrivate GANTrackerPrivate; + +// Google Analytics tracker interface. Tracked pageviews and events are stored +// in a persistent store and dispatched in the background to the server. +@interface GANTracker : NSObject { + @private + GANTrackerPrivate *private_; +} + +// Singleton instance of this class for convenience. ++ (GANTracker *)sharedTracker; + +// Start the tracker by specifying a Google Analytics account ID and a +// dispatch period (in seconds) to dispatch events to the server +// (or -1 to dispatch manually). An optional delegate may be +// supplied. +- (void)startTrackerWithAccountID:(NSString *)accountID + dispatchPeriod:(NSInteger)dispatchPeriod + delegate:(id)delegate; + +// Stop the tracker. +- (void)stopTracker; + +// Track a page view. The pageURL must start with a forward +// slash '/'. Returns YES on success or NO on error (with outErrorOrNULL +// set to the specific error). +- (BOOL)trackPageview:(NSString *)pageURL + withError:(NSError **)error; + +// Track an event. The category and action are required. The label and +// value are optional (specify nil for no label and -1 or any negative integer +// for no value). Returns YES on success or NO on error (with outErrorOrNULL +// set to the specific error). +- (BOOL)trackEvent:(NSString *)category + action:(NSString *)action + label:(NSString *)label + value:(NSInteger)value + withError:(NSError **)error; + +// Set a custom variable. visitor and session scoped custom variables are stored +// for later use. Session and page scoped custom variables are attached to each +// event. Visitor scoped custom variables are sent only on the first event for +// a session. +- (BOOL)setCustomVariableAtIndex:(NSUInteger)index + name:(NSString *)name + value:(NSString *)value + scope:(NSUInteger)scope + withError:(NSError **)error; + +// Set a page scoped custom variable. The variable set is returned with the +// next event only. It will overwrite any existing visitor or session scoped +// custom variables. +- (BOOL)setCustomVariableAtIndex:(NSUInteger)index + name:(NSString *)name + value:(NSString *)value + withError:(NSError **)error; + +// Returns the value of the custom variable at the index requested. Returns +// nil if no variable is found or index is out of range. +- (NSString *) getVisitorCustomVarAtIndex:(NSUInteger)index; + +// Manually dispatch pageviews/events to the server. Returns YES if +// a new dispatch starts. +- (BOOL)dispatch; + +@end + +@protocol GANTrackerDelegate + +// Invoked when a dispatch completes. Reports the number of events +// dispatched and the number of events that failed to dispatch. Failed +// events will be retried on next dispatch. +- (void)trackerDispatchDidComplete:(GANTracker *)tracker + eventsDispatched:(NSUInteger)eventsDispatched + eventsFailedDispatch:(NSUInteger)eventsFailedDispatch; + +@end diff --git a/iPhone/GoogleAnalytics/GoogSDK/libGoogleAnalytics.a b/iPhone/GoogleAnalytics/GoogSDK/libGoogleAnalytics.a new file mode 100644 index 00000000..358e0a54 Binary files /dev/null and b/iPhone/GoogleAnalytics/GoogSDK/libGoogleAnalytics.a differ diff --git a/iPhone/GoogleAnalytics/GoogleAnalyticsPlugin.h b/iPhone/GoogleAnalytics/GoogleAnalyticsPlugin.h new file mode 100644 index 00000000..6b7b53e5 --- /dev/null +++ b/iPhone/GoogleAnalytics/GoogleAnalyticsPlugin.h @@ -0,0 +1,22 @@ +// +// GoogleAnalyticsPlugin.h +// Gapalytics +// +// Created by Jesse MacFadyen on 11-04-21. +// Copyright 2011 Nitobi. All rights reserved. +// + +#import +#import "PhoneGapCommand.h" +#import "GANTracker.h" + + +@interface GoogleAnalyticsPlugin : PhoneGapCommand { + +} + +- (void) startTrackerWithAccountID:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; +- (void) trackEvent:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; +- (void) trackPageview:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; + +@end diff --git a/iPhone/GoogleAnalytics/GoogleAnalyticsPlugin.js b/iPhone/GoogleAnalytics/GoogleAnalyticsPlugin.js new file mode 100755 index 00000000..9c6bb84d --- /dev/null +++ b/iPhone/GoogleAnalytics/GoogleAnalyticsPlugin.js @@ -0,0 +1,51 @@ + +function GoogleAnalyticsPlugin() +{ + +} + +GoogleAnalyticsPlugin.prototype.startTrackerWithAccountID = function(id) +{ + PhoneGap.exec("GoogleAnalyticsPlugin.startTrackerWithAccountID",id); +}; + +GoogleAnalyticsPlugin.prototype.trackPageview = function(pageUri) +{ + PhoneGap.exec("GoogleAnalyticsPlugin.trackPageview",pageUri); +}; + + +GoogleAnalyticsPlugin.prototype.trackEvent = function(category,action,label,value) +{ + var options = {category:category, + action:action, + label:label, + value:value}; + PhoneGap.exec("GoogleAnalyticsPlugin.trackEvent",options); +}; + +GoogleAnalyticsPlugin.prototype.trackerDispatchDidComplete = function(count) +{ + //console.log("trackerDispatchDidComplete :: " + count); +}; + +/** + * Install function + */ +GoogleAnalyticsPlugin.install = function() +{ + if ( !window.plugins ) + { + window.plugins = {}; + } + if ( !window.plugins.googleAnalyticsPlugin ) + { + window.plugins.googleAnalyticsPlugin = new GoogleAnalyticsPlugin(); + } +} + +/** + * Add to PhoneGap constructor + */ +PhoneGap.addConstructor(GoogleAnalyticsPlugin.install); + diff --git a/iPhone/GoogleAnalytics/GoogleAnalyticsPlugin.m b/iPhone/GoogleAnalytics/GoogleAnalyticsPlugin.m new file mode 100644 index 00000000..ba3f3bfe --- /dev/null +++ b/iPhone/GoogleAnalytics/GoogleAnalyticsPlugin.m @@ -0,0 +1,81 @@ +// +// GoogleAnalyticsPlugin.m +// Gapalytics +// +// Created by Jesse MacFadyen on 11-04-21. +// Copyright 2011 Nitobi. All rights reserved. +// + +#import "GoogleAnalyticsPlugin.h" + + + +// Dispatch period in seconds +static const NSInteger kGANDispatchPeriodSec = 10; + + +@implementation GoogleAnalyticsPlugin + +- (void) startTrackerWithAccountID:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + NSString* accountId = [arguments objectAtIndex:0]; + + [[GANTracker sharedTracker] startTrackerWithAccountID:accountId + dispatchPeriod:kGANDispatchPeriodSec + delegate:self]; + +} + +- (void) trackEvent:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + + NSString* category = [options valueForKey:@"category"]; + NSString* action = [options valueForKey:@"action"]; + NSString* label = [options valueForKey:@"label"]; + int value = [[options valueForKey:@"value"] intValue]; + + NSError *error; + + if (![[GANTracker sharedTracker] trackEvent:category + action:action + label:label + value:value + withError:&error]) { + // Handle error here + NSLog(@"GoogleAnalyticsPlugin.trackEvent Error::",[error localizedDescription]); + } + + + NSLog(@"GoogleAnalyticsPlugin.trackEvent::%@, %@, %@, %d",category,action,label,value); + +} + +- (void) trackPageview:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + NSString* pageUri = [arguments objectAtIndex:0]; + NSError *error; + if (![[GANTracker sharedTracker] trackPageview:pageUri + withError:&error]) { + // TODO: Handle error here + } +} + + + +- (void)trackerDispatchDidComplete:(GANTracker *)tracker + eventsDispatched:(NSUInteger)eventsDispatched + eventsFailedDispatch:(NSUInteger)eventsFailedDispatch +{ + NSString* callback = [NSString stringWithFormat:@"window.plugins.googleAnalyticsPlugin.trackerDispatchDidComplete(%d);", + eventsDispatched]; + [ self.webView stringByEvaluatingJavaScriptFromString:callback]; + +} + +- (void) dealloc +{ + [[GANTracker sharedTracker] stopTracker]; + [ super dealloc ]; +} + +@end diff --git a/iPhone/GoogleAnalytics/README b/iPhone/GoogleAnalytics/README new file mode 100755 index 00000000..0714db10 --- /dev/null +++ b/iPhone/GoogleAnalytics/README @@ -0,0 +1,40 @@ + +iOS Plugin for adding Google Analytics to your PhoneGap application. + +Google instructions are here: +http://code.google.com/mobile/analytics/docs/ + +Supported Platforms: + +iOS: + Requirements : + - google lib is included in this repo. in iOS/GoogSDK/ + - Web property ID for analytics + + + Instructions : + - add all files under iOS to the plugins folder of your XCode project ( via XCode Project explorer ) + - add GoogleAnalyticsPlugin.js to your www folder ( via Finder ) + - include GoogleAnalyticsPlugin.js from index.html + + - Google's docs are here : http://code.google.com/mobile/analytics/docs/iphone/ + +Android : + TODO: + + + +Javascript Interface: + + // after device ready, create a local alias and start the tracker with your own id. + var googleAnalytics = window.plugins.googleAnalyticsPlugin; + googleAnalytics.startTrackerWithAccountID("UA-6369089-3"); + + // Track an event in your application + // more here : http://code.google.com/apis/analytics/docs/tracking/eventTrackerGuide.html + googleAnalytics.trackEvent("category","action","label goes here",666); + + // Track an pageview in your application + googleAnalytics.trackPageview(pageURI); + + \ No newline at end of file diff --git a/iPhone/NativeControls/NativeControls.m b/iPhone/NativeControls/NativeControls.m index 8f89c6d3..2257ba76 100644 --- a/iPhone/NativeControls/NativeControls.m +++ b/iPhone/NativeControls/NativeControls.m @@ -206,7 +206,7 @@ - (void)createTabBarItem:(NSArray*)arguments withDict:(NSDictionary*)options UITabBarItem *item = nil; if ([imageName length] > 0) { - UIBarButtonSystemItem systemItem = -1; + UITabBarSystemItem systemItem = -1; if ([imageName isEqualToString:@"tabButton:More"]) systemItem = UITabBarSystemItemMore; if ([imageName isEqualToString:@"tabButton:Favorites"]) systemItem = UITabBarSystemItemFavorites; if ([imageName isEqualToString:@"tabButton:Featured"]) systemItem = UITabBarSystemItemFeatured; @@ -471,7 +471,7 @@ - (void)createToolBarItem:(NSArray*)arguments withDict:(NSDictionary*)options NSString *tagId = [arguments objectAtIndex:0]; NSString *title = [arguments objectAtIndex:1]; - NSString *imageName; + NSString *imageName = nil; if (arguments.count >= 2) { imageName = [arguments objectAtIndex:2]; @@ -507,7 +507,7 @@ - (void)createToolBarItem:(NSArray*)arguments withDict:(NSDictionary*)options UIBarButtonItem *item = nil; if ([imageName length] > 0) { - UIBarButtonSystemItem systemItem; + UIBarButtonSystemItem systemItem = -1; if ([imageName isEqualToString:@"UIBarButtonSystemItemDone"]) { systemItem = UIBarButtonSystemItemDone; diff --git a/iPhone/PrintPlugin/PrintPlugin.js b/iPhone/PrintPlugin/PrintPlugin.js index 7788988d..8d3ebc1f 100644 --- a/iPhone/PrintPlugin/PrintPlugin.js +++ b/iPhone/PrintPlugin/PrintPlugin.js @@ -4,29 +4,32 @@ * MIT licensed */ -var PrintPlugin = function() { - +var PrintPlugin = function() { + } +PrintPlugin.prototype.callbackMap = {}; +PrintPlugin.prototype.callbackIdx = 0; + /* print - html string or DOM node (if latter, innerHTML is used to get the contents). REQUIRED. success - callback function called if print successful. {success: true} fail - callback function called if print unsuccessful. If print fails, {error: reason}. If printing not available: {available: false} - options - {dialogOffset:{left: 0, right: 0}}. Position of popup dialog (iPad only). + options - {dialogOffset:{left: 0, right: 0}}. Position of popup dialog (iPad only). */ PrintPlugin.prototype.print = function(printHTML, success, fail, options) { if (typeof printHTML != 'string'){ console.log("Print function requires an HTML string. Not an object"); return; } - - + + //var printHTML = ""; - + var dialogLeftPos = 0; var dialogTopPos = 0; - - + + if (options){ if (options.dialogOffset){ if (options.dialogOffset.left){ @@ -43,16 +46,35 @@ PrintPlugin.prototype.print = function(printHTML, success, fail, options) { } } } - - - return PhoneGap.exec("PrintPlugin.print", printHTML, GetFunctionName(success), GetFunctionName(fail), dialogLeftPos, dialogTopPos); + + var key = 'print' + this.callbackIdx++; + window.plugins.printPlugin.callbackMap[key] = { + success: function(result) { + delete window.plugins.printPlugin.callbackMap[key]; + success(result); + }, + fail: function(result) { + delete window.plugins.printPlugin.callbackMap[key]; + fail(result); + }, + }; + + var callbackPrefix = 'window.plugins.printPlugin.callbackMap.' + key; + return PhoneGap.exec("PrintPlugin.print", printHTML, callbackPrefix + '.success', callbackPrefix + '.fail', dialogLeftPos, dialogTopPos); }; /* * Callback function returns {available: true/false} */ -PrintPlugin.prototype.isPrintingAvailable = function(result) { - return PhoneGap.exec("PrintPlugin.isPrintingAvailable", GetFunctionName(result)); +PrintPlugin.prototype.isPrintingAvailable = function(callback) { + var key = 'isPrintingAvailable' + this.callbackIdx++; + window.plugins.printPlugin.callbackMap[key] = function(result) { + delete window.plugins.printPlugin.callbackMap[key]; + callback(result); + }; + + var callbackName = 'window.plugins.printPlugin.callbackMap.' + key; + PhoneGap.exec("PrintPlugin.isPrintingAvailable", callbackName); }; PhoneGap.addConstructor(function() { diff --git a/iPhone/README b/iPhone/README.md similarity index 100% rename from iPhone/README rename to iPhone/README.md diff --git a/iPhone/SoundPlug/SoundPlug.h b/iPhone/SoundPlug/SoundPlug.h index 539555a2..b4cd2372 100644 --- a/iPhone/SoundPlug/SoundPlug.h +++ b/iPhone/SoundPlug/SoundPlug.h @@ -1,5 +1,9 @@ #import +#ifdef PHONEGAP_FRAMEWORK +#import +#else #import "PGPlugin.h" +#endif @interface SoundPlug : PGPlugin { } diff --git a/iPhone/Torch/Torch.h b/iPhone/Torch/Torch.h index f086a53e..a1f5b7b3 100644 --- a/iPhone/Torch/Torch.h +++ b/iPhone/Torch/Torch.h @@ -22,5 +22,5 @@ - (void) turnOn:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; - (void) turnOff:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; - +- (void) checkLight:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; @end diff --git a/iPhone/Torch/Torch.js b/iPhone/Torch/Torch.js index 7a8dae21..08486fda 100644 --- a/iPhone/Torch/Torch.js +++ b/iPhone/Torch/Torch.js @@ -7,12 +7,19 @@ // Created by Shazron Abdullah May 26th 2011 // -function Torch() -{ - this._isOn = false; - var self = this; - - this.__defineGetter__("isOn", function() { return self._isOn; }); +function Torch() { + this._isOn = false; + this._checkForLight = false; + var self = this; + + this.__defineGetter__("isOn", function () { + return self._isOn; + }); + + this.__defineGetter__("checkForLight", function () { + return self._checkForLight; + }); + } Torch.prototype.turnOn = function() @@ -25,6 +32,10 @@ Torch.prototype.turnOff = function() PhoneGap.exec("Torch.turnOff"); }; +Torch.prototype.checkLight = function () { + PhoneGap.exec("Torch.checkLight"); +}; + Torch.install = function() { if(!window.plugins) { diff --git a/iPhone/Torch/Torch.m b/iPhone/Torch/Torch.m index 5df3e443..2655b475 100644 --- a/iPhone/Torch/Torch.m +++ b/iPhone/Torch/Torch.m @@ -3,6 +3,7 @@ // PhoneGap Plugin // // Created by Shazron Abdullah May 26th 2011 +// Forked by Michael Pfütze, Jan 11th 2012 // #import "Torch.h" @@ -58,16 +59,19 @@ - (void) setup [captureSession release]; } else { - NSLog(@"Torch not available, hasFlash: %d hasTorch: %d torchMode: %d", + + NSLog(@"Torchblaa not available, hasFlash: %d hasTorch: %d torchMode: %d", captureDevice.hasFlash, captureDevice.hasTorch, captureDevice.torchMode ); + } - + } else { - NSLog(@"Torch not available, AVCaptureDevice class not found."); + NSLog(@"Torchblablabla not available, AVCaptureDevice class not found."); + } } @@ -103,7 +107,7 @@ - (void) turnOff:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)optio [captureDevice setFlashMode:AVCaptureFlashModeOff]; [captureDevice unlockForConfiguration]; - + [super writeJavascript:@"window.plugins.torch._isOn = false;"]; } } @@ -116,4 +120,19 @@ - (void) dealloc [super dealloc]; } +- (void) checkLight:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + AVCaptureDevice* captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([captureDevice hasTorch] && [captureDevice hasFlash] ) { + + [super writeJavascript:@"window.plugins.torch._checkForLight = true;"]; + } + else{ + [super writeJavascript:@"window.plugins.torch._checkForLight = false;"]; + } +} + + + + @end