diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index 6c2852d..65d57e2 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -10,7 +10,7 @@
-
+
@@ -68,8 +68,24 @@
-->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/build.gradle b/android/build.gradle
index 0051ff0..de92470 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -18,6 +18,7 @@ apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
+ compile 'com.android.support:support-v4:25.3.1'
}
android {
@@ -77,5 +78,6 @@ android {
lintOptions {
checkReleaseBuilds false
+ abortOnError false
}
}
diff --git a/android/res/xml/filepaths.xml b/android/res/xml/filepaths.xml
new file mode 100644
index 0000000..6b3e3f8
--- /dev/null
+++ b/android/res/xml/filepaths.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/android/src/de/itsblue/blueROCK/MainActivity.java b/android/src/de/itsblue/blueROCK/MainActivity.java
new file mode 100755
index 0000000..24d2923
--- /dev/null
+++ b/android/src/de/itsblue/blueROCK/MainActivity.java
@@ -0,0 +1,228 @@
+// (c) 2017 Ekkehard Gentz (ekke)
+// this project is based on ideas from
+// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
+// see github project https://github.com/lasconic/ShareUtils-QML
+// also inspired by:
+// https://www.androidcode.ninja/android-share-intent-example/
+// https://www.calligra.org/blogs/sharing-with-qt-on-android/
+// https://stackoverflow.com/questions/7156932/open-file-in-another-app
+// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
+// OpenURL in At Android: got ideas from:
+// https://github.com/BernhardWenzel/open-url-in-qt-android
+// https://github.com/tobiatesan/android_intents_qt
+//
+// see also /COPYRIGHT and /LICENSE
+
+package de.itsblue.blueROCK;
+
+import org.qtproject.qt5.android.QtNative;
+
+import org.qtproject.qt5.android.bindings.QtActivity;
+import android.os.*;
+import android.content.*;
+import android.app.*;
+
+import java.lang.String;
+import android.content.Intent;
+import java.io.File;
+import android.net.Uri;
+import android.util.Log;
+import android.content.ContentResolver;
+import android.webkit.MimeTypeMap;
+
+import org.ekkescorner.utils.*;
+
+
+
+public class MainActivity extends QtActivity
+{
+ // native - must be implemented in Cpp via JNI
+ // 'file' scheme or resolved from 'content' scheme:
+ public static native void setFileUrlReceived(String url);
+ // InputStream from 'content' scheme:
+ public static native void setFileReceivedAndSaved(String url);
+ //
+ public static native void fireActivityResult(int requestCode, int resultCode);
+ //
+ public static native boolean checkFileExits(String url);
+
+ public static boolean isIntentPending;
+ public static boolean isInitialized;
+ public static String workingDirPath;
+
+ // Use a custom Chooser without providing own App as share target !
+ // see QShareUtils.java createCustomChooserAndStartActivity()
+ // Selecting your own App as target could cause AndroidOS to call
+ // onCreate() instead of onNewIntent()
+ // and then you are in trouble because we're using 'singleInstance' as LaunchMode
+ // more details: my blog at Qt
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d("ekkescorner", "onCreate QShareActivity");
+ // now we're checking if the App was started from another Android App via Intent
+ Intent theIntent = getIntent();
+ if (theIntent != null){
+ String theAction = theIntent.getAction();
+ if (theAction != null){
+ Log.d("ekkescorner onCreate ", theAction);
+ // QML UI not ready yet
+ // delay processIntent();
+ isIntentPending = true;
+ }
+ }
+ } // onCreate
+
+ // WIP - trying to find a solution to survive a 2nd onCreate
+ // ongoing discussion in QtMob (Slack)
+ // from other Apps not respecting that you only have a singleInstance
+ // there are problems per ex. sharing a file from Google Files App,
+ // but working well using Xiaomi FileManager App
+ @Override
+ public void onDestroy() {
+ Log.d("ekkescorner", "onDestroy QShareActivity");
+ // super.onDestroy();
+ // System.exit() closes the App before doing onCreate() again
+ // then the App was restarted, but looses context
+ // This works for Samsung My Files
+ // but Google Files doesn't call onDestroy()
+ System.exit(0);
+ }
+
+ // we start Activity with result code
+ // to test JNI with QAndroidActivityResultReceiver you must comment or rename
+ // this method here - otherwise you'll get wrong request or result codes
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // Check which request we're responding to
+ Log.d("ekkescorner onActivityResult", "requestCode: "+requestCode);
+ if (resultCode == RESULT_OK) {
+ Log.d("ekkescorner onActivityResult - resultCode: ", "SUCCESS");
+ } else {
+ Log.d("ekkescorner onActivityResult - resultCode: ", "CANCEL");
+ }
+ // hint: result comes back too fast for Action SEND
+ // if you want to delete/move the File add a Timer w 500ms delay
+ // see Example App main.qml - delayDeleteTimer
+ // if you want to revoke permissions for older OS
+ // it makes sense also do this after the delay
+ fireActivityResult(requestCode, resultCode);
+ }
+
+ // if we are opened from other apps:
+ @Override
+ public void onNewIntent(Intent intent) {
+ Log.d("ekkescorner", "onNewIntent");
+ super.onNewIntent(intent);
+ setIntent(intent);
+ // Intent will be processed, if all is initialized and Qt / QML can handle the event
+ if(isInitialized) {
+ processIntent();
+ } else {
+ isIntentPending = true;
+ }
+ } // onNewIntent
+
+ public void checkPendingIntents(String workingDir) {
+ isInitialized = true;
+ workingDirPath = workingDir;
+ Log.d("ekkescorner", workingDirPath);
+ if(isIntentPending) {
+ isIntentPending = false;
+ Log.d("ekkescorner", "checkPendingIntents: true");
+ processIntent();
+ } else {
+ Log.d("ekkescorner", "nothingPending");
+ }
+ } // checkPendingIntents
+
+ // process the Intent if Action is SEND or VIEW
+ private void processIntent(){
+ Intent intent = getIntent();
+
+ Uri intentUri;
+ String intentScheme;
+ String intentAction;
+ // we are listening to android.intent.action.SEND or VIEW (see Manifest)
+ if (intent.getAction().equals("android.intent.action.VIEW")){
+ intentAction = "VIEW";
+ intentUri = intent.getData();
+ } else if (intent.getAction().equals("android.intent.action.SEND")){
+ intentAction = "SEND";
+ Bundle bundle = intent.getExtras();
+ intentUri = (Uri)bundle.get(Intent.EXTRA_STREAM);
+ } else {
+ Log.d("ekkescorner Intent unknown action:", intent.getAction());
+ return;
+ }
+ Log.d("ekkescorner action:", intentAction);
+ if (intentUri == null){
+ Log.d("ekkescorner Intent URI:", "is null");
+ return;
+ }
+
+ Log.d("ekkescorner Intent URI:", intentUri.toString());
+
+ // content or file
+ intentScheme = intentUri.getScheme();
+ if (intentScheme == null){
+ Log.d("ekkescorner Intent URI Scheme:", "is null");
+ return;
+ }
+ if(intentScheme.equals("file")){
+ // URI as encoded string
+ Log.d("ekkescorner Intent File URI: ", intentUri.toString());
+ setFileUrlReceived(intentUri.toString());
+ // we are done Qt can deal with file scheme
+ return;
+ }
+ if(!intentScheme.equals("content")){
+ Log.d("ekkescorner Intent URI unknown scheme: ", intentScheme);
+ return;
+ }
+ // ok - it's a content scheme URI
+ // we will try to resolve the Path to a File URI
+ // if this won't work or if the File cannot be opened,
+ // we'll try to copy the file into our App working dir via InputStream
+ // hopefully in most cases PathResolver will give a path
+
+ // you need the file extension, MimeType or Name from ContentResolver ?
+ // here's HowTo get it:
+ Log.d("ekkescorner Intent Content URI: ", intentUri.toString());
+ ContentResolver cR = this.getContentResolver();
+ MimeTypeMap mime = MimeTypeMap.getSingleton();
+ String fileExtension = mime.getExtensionFromMimeType(cR.getType(intentUri));
+ Log.d("ekkescorner","Intent extension: "+fileExtension);
+ String mimeType = cR.getType(intentUri);
+ Log.d("ekkescorner"," Intent MimeType: "+mimeType);
+ String name = QShareUtils.getContentName(cR, intentUri);
+ if(name != null) {
+ Log.d("ekkescorner Intent Name:", name);
+ } else {
+ Log.d("ekkescorner Intent Name:", "is NULL");
+ }
+ String filePath;
+ filePath = QSharePathResolver.getRealPathFromURI(this, intentUri);
+ if(filePath == null) {
+ Log.d("ekkescorner QSharePathResolver:", "filePath is NULL");
+ } else {
+ Log.d("ekkescorner QSharePathResolver:", filePath);
+ // to be safe check if this File Url really can be opened by Qt
+ // there were problems with MS office apps on Android 7
+ if (checkFileExits(filePath)) {
+ setFileUrlReceived(filePath);
+ // we are done Qt can deal with file scheme
+ return;
+ }
+ }
+
+ // trying the InputStream way:
+ filePath = QShareUtils.createFile(cR, intentUri, workingDirPath);
+ if(filePath == null) {
+ Log.d("ekkescorner Intent FilePath:", "is NULL");
+ return;
+ }
+ setFileReceivedAndSaved(filePath);
+ } // processIntent
+
+} // class QShareActivity
diff --git a/android/src/org/ekkescorner/utils/QSharePathResolver.java b/android/src/org/ekkescorner/utils/QSharePathResolver.java
new file mode 100644
index 0000000..f93d1a8
--- /dev/null
+++ b/android/src/org/ekkescorner/utils/QSharePathResolver.java
@@ -0,0 +1,223 @@
+// from: https://github.com/wkh237/react-native-fetch-blob/blob/master/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java
+// MIT License, see: https://github.com/wkh237/react-native-fetch-blob/blob/master/LICENSE
+// original copyright: Copyright (c) 2017 xeiyan@gmail.com
+// src slightly modified to be used into Qt Projects: (c) 2017 ekke@ekkes-corner.org
+
+package org.ekkescorner.utils;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.content.ContentUris;
+import android.os.Environment;
+import android.content.ContentResolver;
+import java.io.File;
+import java.io.InputStream;
+import java.io.FileOutputStream;
+import android.util.Log;
+import java.lang.NumberFormatException;
+
+public class QSharePathResolver {
+ public static String getRealPathFromURI(final Context context, final Uri uri) {
+
+ final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+ // DocumentProvider
+ if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ Log.d("ekkescorner"," isExternalStorageDocument");
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ if ("primary".equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
+ }
+
+ // TODO handle non-primary volumes
+ }
+ // DownloadsProvider
+ else if (isDownloadsDocument(uri)) {
+ Log.d("ekkescorner"," isDownloadsDocument");
+ final String id = DocumentsContract.getDocumentId(uri);
+ Log.d("ekkescorner"," getDocumentId "+id);
+ long longId = 0;
+ try
+ {
+ longId = Long.valueOf(id);
+ }
+ catch(NumberFormatException nfe)
+ {
+ return getDataColumn(context, uri, null, null);
+ }
+ final Uri contentUri = ContentUris.withAppendedId(
+ Uri.parse("content://downloads/public_downloads"), longId);
+
+ return getDataColumn(context, contentUri, null, null);
+ }
+ // MediaProvider
+ else if (isMediaDocument(uri)) {
+ Log.d("ekkescorner"," isMediaDocument");
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[] {
+ split[1]
+ };
+
+ return getDataColumn(context, contentUri, selection, selectionArgs);
+ }
+ else if ("content".equalsIgnoreCase(uri.getScheme())) {
+ Log.d("ekkescorner"," is uri.getScheme()");
+ // Return the remote address
+ if (isGooglePhotosUri(uri))
+ return uri.getLastPathSegment();
+
+ return getDataColumn(context, uri, null, null);
+ }
+ // Other Providers
+ else{
+ Log.d("ekkescorner ","is Other Provider");
+ try {
+ InputStream attachment = context.getContentResolver().openInputStream(uri);
+ if (attachment != null) {
+ String filename = getContentName(context.getContentResolver(), uri);
+ if (filename != null) {
+ File file = new File(context.getCacheDir(), filename);
+ FileOutputStream tmp = new FileOutputStream(file);
+ byte[] buffer = new byte[1024];
+ while (attachment.read(buffer) > 0) {
+ tmp.write(buffer);
+ }
+ tmp.close();
+ attachment.close();
+ return file.getAbsolutePath();
+ }
+ }
+ } catch (Exception e) {
+ // TODO SIGNAL shareError()
+ return null;
+ }
+ }
+ }
+ // MediaStore (and general)
+ else if ("content".equalsIgnoreCase(uri.getScheme())) {
+ Log.d("ekkescorner ","NOT DocumentsContract.isDocumentUri");
+ Log.d("ekkescorner"," is uri.getScheme()");
+ // Return the remote address
+ if (isGooglePhotosUri(uri))
+ return uri.getLastPathSegment();
+ Log.d("ekkescorner"," return: getDataColumn ");
+ return getDataColumn(context, uri, null, null);
+ }
+ // File
+ else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ Log.d("ekkescorner ","NOT DocumentsContract.isDocumentUri");
+ Log.d("ekkescorner"," is file scheme");
+ return uri.getPath();
+ }
+
+ return null;
+ }
+
+ private static String getContentName(ContentResolver resolver, Uri uri) {
+ Cursor cursor = resolver.query(uri, null, null, null, null);
+ cursor.moveToFirst();
+ int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
+ if (nameIndex >= 0) {
+ String name = cursor.getString(nameIndex);
+ cursor.close();
+ return name;
+ }
+ cursor.close();
+ return null;
+ }
+
+ /**
+ * Get the value of the data column for this Uri. This is useful for
+ * MediaStore Uris, and other file-based ContentProviders.
+ *
+ * @param context The context.
+ * @param uri The Uri to query.
+ * @param selection (Optional) Filter used in the query.
+ * @param selectionArgs (Optional) Selection arguments used in the query.
+ * @return The value of the _data column, which is typically a file path.
+ */
+ public static String getDataColumn(Context context, Uri uri, String selection,
+ String[] selectionArgs) {
+
+ Cursor cursor = null;
+ String result = null;
+ final String column = "_data";
+ final String[] projection = {
+ column
+ };
+
+ try {
+ cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ result = cursor.getString(index);
+ }
+ }
+ catch (Exception ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return result;
+ }
+
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is ExternalStorageProvider.
+ */
+ public static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is DownloadsProvider.
+ */
+ public static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is MediaProvider.
+ */
+ public static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is Google Photos.
+ */
+ public static boolean isGooglePhotosUri(Uri uri) {
+ return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+ }
+
+}
diff --git a/android/src/org/ekkescorner/utils/QShareUtils.java b/android/src/org/ekkescorner/utils/QShareUtils.java
new file mode 100755
index 0000000..38a07a7
--- /dev/null
+++ b/android/src/org/ekkescorner/utils/QShareUtils.java
@@ -0,0 +1,396 @@
+// (c) 2017 Ekkehard Gentz (ekke)
+// this project is based on ideas from
+// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
+// see github project https://github.com/lasconic/ShareUtils-QML
+// also inspired by:
+// https://www.androidcode.ninja/android-share-intent-example/
+// https://www.calligra.org/blogs/sharing-with-qt-on-android/
+// https://stackoverflow.com/questions/7156932/open-file-in-another-app
+// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
+// https://stackoverflow.com/questions/5734678/custom-filtering-of-intent-chooser-based-on-installed-android-package-name
+// see also /COPYRIGHT and /LICENSE
+
+package org.ekkescorner.utils;
+
+import org.qtproject.qt5.android.QtNative;
+
+import java.lang.String;
+import android.content.Intent;
+import java.io.File;
+import android.net.Uri;
+import android.util.Log;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.provider.MediaStore;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.FileOutputStream;
+
+import java.util.List;
+import android.content.pm.ResolveInfo;
+import java.util.ArrayList;
+import android.content.pm.PackageManager;
+import java.util.Comparator;
+import java.util.Collections;
+import android.content.Context;
+import android.os.Parcelable;
+
+import android.os.Build;
+
+import android.support.v4.content.FileProvider;
+import android.support.v4.app.ShareCompat;
+
+public class QShareUtils
+{
+ // reference Authority as defined in AndroidManifest.xml
+ private static String AUTHORITY="de.itsblue.blueROCK.fileprovider";
+
+ protected QShareUtils()
+ {
+ Log.d("ekkescorner", "QShareUtils()");
+ }
+
+ public static boolean checkMimeTypeView(String mimeType) {
+ if (QtNative.activity() == null)
+ return false;
+ Intent myIntent = new Intent();
+ myIntent.setAction(Intent.ACTION_VIEW);
+ // without an URI resolve always fails
+ // an empty URI allows to resolve the Activity
+ File fileToShare = new File("");
+ Uri uri = Uri.fromFile(fileToShare);
+ myIntent.setDataAndType(uri, mimeType);
+
+ // Verify that the intent will resolve to an activity
+ if (myIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
+ Log.d("ekkescorner checkMime ", "YEP - we can go on and View");
+ return true;
+ } else {
+ Log.d("ekkescorner checkMime", "sorry - no App available to View");
+ }
+ return false;
+ }
+
+ public static boolean checkMimeTypeEdit(String mimeType) {
+ if (QtNative.activity() == null)
+ return false;
+ Intent myIntent = new Intent();
+ myIntent.setAction(Intent.ACTION_EDIT);
+ // without an URI resolve always fails
+ // an empty URI allows to resolve the Activity
+ File fileToShare = new File("");
+ Uri uri = Uri.fromFile(fileToShare);
+ myIntent.setDataAndType(uri, mimeType);
+
+ // Verify that the intent will resolve to an activity
+ if (myIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
+ Log.d("ekkescorner checkMime ", "YEP - we can go on and Edit");
+ return true;
+ } else {
+ Log.d("ekkescorner checkMime", "sorry - no App available to Edit");
+ }
+ return false;
+ }
+
+ public static boolean shareText(String text) {
+ if (QtNative.activity() == null)
+ return false;
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, text);
+ sendIntent.setType("text/plain");
+
+ // Verify that the intent will resolve to an activity
+ if (sendIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
+ QtNative.activity().startActivity(sendIntent);
+ return true;
+ } else {
+ Log.d("ekkescorner share", "Intent not resolved");
+ }
+ return false;
+ }
+
+ // thx @oxied and @pooks for the idea: https://stackoverflow.com/a/18835895/135559
+ // theIntent is already configured with all needed properties and flags
+ // so we only have to add the packageName of targeted app
+ public static boolean createCustomChooserAndStartActivity(Intent theIntent, String title, int requestId, Uri uri) {
+ final Context context = QtNative.activity();
+ final PackageManager packageManager = context.getPackageManager();
+ final boolean isLowerOrEqualsKitKat = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT;
+
+ // MATCH_DEFAULT_ONLY: Resolution and querying flag. if set, only filters that support the CATEGORY_DEFAULT will be considered for matching.
+ // Check if there is a default app for this type of content.
+ ResolveInfo defaultAppInfo = packageManager.resolveActivity(theIntent, PackageManager.MATCH_DEFAULT_ONLY);
+ if(defaultAppInfo == null) {
+ Log.d("ekkescorner", title+" PackageManager cannot resolve Activity");
+ return false;
+ }
+
+ // had to remove this check - there can be more Activity names, per ex
+ // com.google.android.apps.docs.editors.kix.quickword.QuickWordDocumentOpenerActivityAlias
+ // if (!defaultAppInfo.activityInfo.name.endsWith("ResolverActivity") && !defaultAppInfo.activityInfo.name.endsWith("EditActivity")) {
+ // Log.d("ekkescorner", title+" defaultAppInfo not Resolver or EditActivity: "+defaultAppInfo.activityInfo.name);
+ // return false;
+ //}
+
+ // Retrieve all apps for our intent. Check if there are any apps returned
+ List appInfoList = packageManager.queryIntentActivities(theIntent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (appInfoList.isEmpty()) {
+ Log.d("ekkescorner", title+" appInfoList.isEmpty");
+ return false;
+ }
+ Log.d("ekkescorner", title+" appInfoList: "+appInfoList.size());
+
+ // Sort in alphabetical order
+ Collections.sort(appInfoList, new Comparator() {
+ @Override
+ public int compare(ResolveInfo first, ResolveInfo second) {
+ String firstName = first.loadLabel(packageManager).toString();
+ String secondName = second.loadLabel(packageManager).toString();
+ return firstName.compareToIgnoreCase(secondName);
+ }
+ });
+
+ List targetedIntents = new ArrayList();
+ // Filter itself and create intent with the rest of the apps.
+ for (ResolveInfo appInfo : appInfoList) {
+ // get the target PackageName
+ String targetPackageName = appInfo.activityInfo.packageName;
+ // we don't want to share with our own app
+ // in fact sharing with own app with resultCode will crash because doesn't work well with launch mode 'singleInstance'
+ if (targetPackageName.equals(context.getPackageName())) {
+ continue;
+ }
+ // if you have a blacklist of apps please exclude them here
+
+ // we create the targeted Intent based on our already configured Intent
+ Intent targetedIntent = new Intent(theIntent);
+ // now add the target packageName so this Intent will only find the one specific App
+ targetedIntent.setPackage(targetPackageName);
+ // collect all these targetedIntents
+ targetedIntents.add(targetedIntent);
+
+ // legacy support and Workaround for Android bug
+ // grantUriPermission needed for KITKAT or older
+ // see https://code.google.com/p/android/issues/detail?id=76683
+ // also: https://stackoverflow.com/questions/18249007/how-to-use-support-fileprovider-for-sharing-content-to-other-apps
+ if(isLowerOrEqualsKitKat) {
+ Log.d("ekkescorner", "legacy support grantUriPermission");
+ context.grantUriPermission(targetPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ // attention: you must revoke the permission later, so this only makes sense with getting back a result to know that Intent was done
+ // I always move or delete the file, so I don't revoke permission
+ }
+ }
+
+ // check if there are apps found for our Intent to avoid that there was only our own removed app before
+ if (targetedIntents.isEmpty()) {
+ Log.d("ekkescorner", title+" targetedIntents.isEmpty");
+ return false;
+ }
+
+ // now we can create our Intent with custom Chooser
+ // we need all collected targetedIntents as EXTRA_INITIAL_INTENTS
+ // we're using the last targetedIntent as initializing Intent, because
+ // chooser adds its initializing intent to the end of EXTRA_INITIAL_INTENTS :)
+ Intent chooserIntent = Intent.createChooser(targetedIntents.remove(targetedIntents.size() - 1), title);
+ if (targetedIntents.isEmpty()) {
+ Log.d("ekkescorner", title+" only one Intent left for Chooser");
+ } else {
+ chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toArray(new Parcelable[] {}));
+ }
+ // Verify that the intent will resolve to an activity
+ if (chooserIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
+ if(requestId > 0) {
+ QtNative.activity().startActivityForResult(chooserIntent, requestId);
+ } else {
+ QtNative.activity().startActivity(chooserIntent);
+ }
+ return true;
+ }
+ Log.d("ekkescorner", title+" Chooser Intent not resolved. Should never happen");
+ return false;
+ }
+
+ // I am deleting the files from shared folder when Activity was done or canceled
+ // so probably I don't have to revike FilePermissions for older OS
+ // if you don't delete or move the file: here's what you must done to revoke the access
+ public static void revokeFilePermissions(String filePath) {
+ final Context context = QtNative.activity();
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
+ File file = new File(filePath);
+ Uri uri = FileProvider.getUriForFile(context, AUTHORITY, file);
+ context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ }
+ }
+
+ public static boolean sendFile(String filePath, String title, String mimeType, int requestId) {
+ if (QtNative.activity() == null)
+ return false;
+
+ // using v4 support library create the Intent from ShareCompat
+ // Intent sendIntent = new Intent();
+ Intent sendIntent = ShareCompat.IntentBuilder.from(QtNative.activity()).getIntent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+
+ File imageFileToShare = new File(filePath);
+
+ // Using FileProvider you must get the URI from FileProvider using your AUTHORITY
+ // Uri uri = Uri.fromFile(imageFileToShare);
+ Uri uri;
+ try {
+ uri = FileProvider.getUriForFile(QtNative.activity(), AUTHORITY, imageFileToShare);
+ } catch (IllegalArgumentException e) {
+ Log.d("ekkescorner sendFile - cannot be shared: ", filePath);
+ return false;
+ }
+
+ Log.d("ekkescorner sendFile", uri.toString());
+ sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
+
+ if(mimeType == null || mimeType.isEmpty()) {
+ // fallback if mimeType not set
+ mimeType = QtNative.activity().getContentResolver().getType(uri);
+ Log.d("ekkescorner sendFile guessed mimeType:", mimeType);
+ } else {
+ Log.d("ekkescorner sendFile w mimeType:", mimeType);
+ }
+
+ sendIntent.setType(mimeType);
+
+ sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ sendIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ return createCustomChooserAndStartActivity(sendIntent, title, requestId, uri);
+ }
+
+ public static boolean viewFile(String filePath, String title, String mimeType, int requestId) {
+ if (QtNative.activity() == null)
+ return false;
+
+ // using v4 support library create the Intent from ShareCompat
+ // Intent viewIntent = new Intent();
+ Intent viewIntent = ShareCompat.IntentBuilder.from(QtNative.activity()).getIntent();
+ viewIntent.setAction(Intent.ACTION_VIEW);
+
+ File imageFileToShare = new File(filePath);
+
+ // Using FileProvider you must get the URI from FileProvider using your AUTHORITY
+ // Uri uri = Uri.fromFile(imageFileToShare);
+ Uri uri;
+ try {
+ uri = FileProvider.getUriForFile(QtNative.activity(), AUTHORITY, imageFileToShare);
+ } catch (IllegalArgumentException e) {
+ Log.d("ekkescorner viewFile - cannot be shared: ", filePath);
+ return false;
+ }
+ // now we got a content URI per ex
+ // content://org.ekkescorner.examples.sharex.fileprovider/my_shared_files/qt-logo.png
+ // from a fileUrl:
+ // /data/user/0/org.ekkescorner.examples.sharex/files/share_example_x_files/qt-logo.png
+ Log.d("ekkescorner viewFile from file path: ", filePath);
+ Log.d("ekkescorner viewFile to content URI: ", uri.toString());
+
+ if(mimeType == null || mimeType.isEmpty()) {
+ // fallback if mimeType not set
+ mimeType = QtNative.activity().getContentResolver().getType(uri);
+ Log.d("ekkescorner viewFile guessed mimeType:", mimeType);
+ } else {
+ Log.d("ekkescorner viewFile w mimeType:", mimeType);
+ }
+
+ viewIntent.setDataAndType(uri, mimeType);
+
+ viewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ viewIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ return createCustomChooserAndStartActivity(viewIntent, title, requestId, uri);
+ }
+
+ public static boolean editFile(String filePath, String title, String mimeType, int requestId) {
+ if (QtNative.activity() == null)
+ return false;
+
+ // using v4 support library create the Intent from ShareCompat
+ // Intent editIntent = new Intent();
+ Intent editIntent = ShareCompat.IntentBuilder.from(QtNative.activity()).getIntent();
+ editIntent.setAction(Intent.ACTION_EDIT);
+
+ File imageFileToShare = new File(filePath);
+
+ // Using FileProvider you must get the URI from FileProvider using your AUTHORITY
+ // Uri uri = Uri.fromFile(imageFileToShare);
+ Uri uri;
+ try {
+ uri = FileProvider.getUriForFile(QtNative.activity(), AUTHORITY, imageFileToShare);
+ } catch (IllegalArgumentException e) {
+ Log.d("ekkescorner editFile - cannot be shared: ", filePath);
+ return false;
+ }
+ Log.d("ekkescorner editFile", uri.toString());
+
+ if(mimeType == null || mimeType.isEmpty()) {
+ // fallback if mimeType not set
+ mimeType = QtNative.activity().getContentResolver().getType(uri);
+ Log.d("ekkescorner editFile guessed mimeType:", mimeType);
+ } else {
+ Log.d("ekkescorner editFile w mimeType:", mimeType);
+ }
+
+ editIntent.setDataAndType(uri, mimeType);
+
+ editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ return createCustomChooserAndStartActivity(editIntent, title, requestId, uri);
+ }
+
+ public static String getContentName(ContentResolver cR, Uri uri) {
+ Cursor cursor = cR.query(uri, null, null, null, null);
+ cursor.moveToFirst();
+ int nameIndex = cursor
+ .getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
+ if (nameIndex >= 0) {
+ return cursor.getString(nameIndex);
+ } else {
+ return null;
+ }
+ }
+
+ public static String createFile(ContentResolver cR, Uri uri, String fileLocation) {
+ String filePath = null;
+ try {
+ InputStream iStream = cR.openInputStream(uri);
+ if (iStream != null) {
+ String name = getContentName(cR, uri);
+ if (name != null) {
+ filePath = fileLocation + "/" + name;
+ Log.d("ekkescorner - create File", filePath);
+ File f = new File(filePath);
+ FileOutputStream tmp = new FileOutputStream(f);
+ Log.d("ekkescorner - create File", "new FileOutputStream");
+
+ byte[] buffer = new byte[1024];
+ while (iStream.read(buffer) > 0) {
+ tmp.write(buffer);
+ }
+ tmp.close();
+ iStream.close();
+ return filePath;
+ } // name
+ } // iStream
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ return filePath;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return filePath;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return filePath;
+ }
+ return filePath;
+ }
+
+}
diff --git a/blueROCK.pro b/blueROCK.pro
index 5708afc..a5c666b 100644
--- a/blueROCK.pro
+++ b/blueROCK.pro
@@ -1,4 +1,4 @@
-QT += quick qml quickcontrols2 purchasing widgets
+QT += quick qml quickcontrols2 purchasing printsupport
CONFIG += c++11
VERSION = 0.5.0
@@ -15,10 +15,23 @@ DEFINES += QT_DEPRECATED_WARNINGS
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+INCLUDEPATH *= $$PWD/headers
+
+# Add version to define
+DEFINES += APP_VERSION=\"\\\"$${VERSION}\\\"\"
+
SOURCES += \
+ sources/shareUtils/platformshareutils.cpp \
sources/appsettings.cpp \
sources/bluerockbackend.cpp \
- sources/main.cpp
+ sources/shareUtils/shareutils.cpp \
+ sources/main.cpp
+
+HEADERS += \
+ headers/appsettings.h \
+ headers/bluerockbackend.h \
+ headers/shareUtils/shareutils.h \
+ headers/shareUtils/platformshareutils.h
RESOURCES += resources/qml/qml.qrc \
resources/shared/shared.qrc \
@@ -37,27 +50,28 @@ qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
-# Add version to define
-DEFINES += APP_VERSION=\"\\\"$${VERSION}\\\"\"
-
-HEADERS += \
- headers/appsettings.h \
- headers/bluerockbackend.h
-
DISTFILES += \
- CHANGELOG.md \
- android/AndroidManifest.xml \
- android/build.gradle \
- android/gradle.properties \
- android/gradle/wrapper/gradle-wrapper.jar \
- android/gradle/wrapper/gradle-wrapper.properties \
- android/gradlew \
- android/gradlew.bat \
- android/res/values/libs.xml
+ CHANGELOG.md
android {
QT += androidextras
+ SOURCES += sources/shareUtils/androidshareutils.cpp
+ HEADERS += headers/shareUtils/androidshareutils.h
+
+ OTHER_FILES += android/src/org/ekkescorner/utils/QShareUtils.java \
+ android/src/org/ekkescorner/utils/QSharePathResolver.java \
+ android/src/de/itsblue/blueROCK/MainActivity.java \
+ android/AndroidManifest.xml \
+ android/build.gradle \
+ android/gradle.properties \
+ android/gradle/wrapper/gradle-wrapper.jar \
+ android/gradle/wrapper/gradle-wrapper.properties \
+ android/gradlew \
+ android/gradlew.bat \
+ android/res/values/libs.xml \
+ android/res/xml/filepaths.xml
+
defineReplace(droidVersionCode) {
segments = $$split(1, ".")
for (segment, segments): vCode = "$$first(vCode)$$format_number($$segment, width=3 zeropad)"
diff --git a/headers/bluerockbackend.h b/headers/bluerockbackend.h
index 091b9be..b03b146 100644
--- a/headers/bluerockbackend.h
+++ b/headers/bluerockbackend.h
@@ -24,6 +24,17 @@
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "shareUtils/shareutils.h"
class BlueRockBackend : public QObject
{
@@ -34,13 +45,16 @@ public:
private:
QVariantMap senddata(QUrl serviceUrl, QUrlQuery pdata = QUrlQuery());
+ ShareUtils* _shareUtils;
+
signals:
public slots:
QVariant getWidgetData(QVariantMap params);
-
QVariantMap getParamsFromUrl(QString url);
+ void shareResultsAsUrl(QString url);
+ void shareResultsAsPoster(QString url);
};
diff --git a/headers/shareUtils/androidshareutils.h b/headers/shareUtils/androidshareutils.h
new file mode 100755
index 0000000..50786fd
--- /dev/null
+++ b/headers/shareUtils/androidshareutils.h
@@ -0,0 +1,48 @@
+// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
+// my blog about Qt for mobile: http://j.mp/qt-x
+// see also /COPYRIGHT and /LICENSE
+
+#ifndef ANDROIDSHAREUTILS_H
+#define ANDROIDSHAREUTILS_H
+
+#include
+#include
+
+#include "shareUtils/platformshareutils.h"
+
+class AndroidShareUtils : public PlatformShareUtils, public QAndroidActivityResultReceiver
+{
+public:
+ AndroidShareUtils(QObject* parent = nullptr);
+ bool checkMimeTypeView(const QString &mimeType) override;
+ bool checkMimeTypeEdit(const QString &mimeType) override;
+ void shareText(const QString &text) override;
+ void sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
+ void viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
+ void editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
+
+ void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override;
+ void onActivityResult(int requestCode, int resultCode);
+
+ void checkPendingIntents(const QString workingDirPath) override;
+
+ static AndroidShareUtils* getInstance();
+
+public slots:
+ void setFileUrlReceived(const QString &url);
+ void setFileReceivedAndSaved(const QString &url);
+ bool checkFileExits(const QString &url);
+
+private:
+ bool mIsEditMode;
+ qint64 mLastModified;
+ QString mCurrentFilePath;
+
+ static AndroidShareUtils* mInstance;
+
+ void processActivityResult(int requestCode, int resultCode);
+
+};
+
+
+#endif // ANDROIDSHAREUTILS_H
diff --git a/headers/shareUtils/platformshareutils.h b/headers/shareUtils/platformshareutils.h
new file mode 100644
index 0000000..504983f
--- /dev/null
+++ b/headers/shareUtils/platformshareutils.h
@@ -0,0 +1,46 @@
+// (c) 2017 Ekkehard Gentz (ekke)
+// this project is based on ideas from
+// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
+// see github project https://github.com/lasconic/ShareUtils-QML
+// also inspired by:
+// https://www.androidcode.ninja/android-share-intent-example/
+// https://www.calligra.org/blogs/sharing-with-qt-on-android/
+// https://stackoverflow.com/questions/7156932/open-file-in-another-app
+// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
+// see also /COPYRIGHT and /LICENSE
+
+// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
+// my blog about Qt for mobile: http://j.mp/qt-x
+// see also /COPYRIGHT and /LICENSE
+
+#ifndef PLATFORMSHAREUTILS_H
+#define PLATFORMSHAREUTILS_H
+
+#include
+#include
+
+class PlatformShareUtils : public QObject
+{
+ Q_OBJECT
+signals:
+ void shareEditDone(int requestCode);
+ void shareFinished(int requestCode);
+ void shareNoAppAvailable(int requestCode);
+ void shareError(int requestCode, QString message);
+ void fileUrlReceived(QString url);
+ void fileReceivedAndSaved(QString url);
+
+public:
+ PlatformShareUtils(QObject *parent = 0);
+ virtual ~PlatformShareUtils();
+ virtual bool checkMimeTypeView(const QString &mimeType);
+ virtual bool checkMimeTypeEdit(const QString &mimeType);
+ virtual void shareText(const QString &text);
+ virtual void sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
+ virtual void viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
+ virtual void editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
+
+ virtual void checkPendingIntents(const QString workingDirPath);
+};
+
+#endif // PLATFORMSHAREUTILS_H
diff --git a/headers/shareUtils/shareutils.h b/headers/shareUtils/shareutils.h
new file mode 100755
index 0000000..956a316
--- /dev/null
+++ b/headers/shareUtils/shareutils.h
@@ -0,0 +1,60 @@
+// (c) 2017 Ekkehard Gentz (ekke)
+// this project is based on ideas from
+// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
+// see github project https://github.com/lasconic/ShareUtils-QML
+// also inspired by:
+// https://www.androidcode.ninja/android-share-intent-example/
+// https://www.calligra.org/blogs/sharing-with-qt-on-android/
+// https://stackoverflow.com/questions/7156932/open-file-in-another-app
+// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
+// see also /COPYRIGHT and /LICENSE
+
+// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
+// my blog about Qt for mobile: http://j.mp/qt-x
+// see also /COPYRIGHT and /LICENSE
+
+#ifndef SHAREUTILS_H
+#define SHAREUTILS_H
+
+#include
+#include
+
+#include "shareUtils/platformshareutils.h"
+
+class ShareUtils : public QObject
+{
+ Q_OBJECT
+
+
+signals:
+ void shareEditDone(int requestCode);
+ void shareFinished(int requestCode);
+ void shareNoAppAvailable(int requestCode);
+ void shareError(int requestCode, QString message);
+ void fileUrlReceived(QString url);
+ void fileReceivedAndSaved(QString url);
+
+public slots:
+ void onShareEditDone(int requestCode);
+ void onShareFinished(int requestCode);
+ void onShareNoAppAvailable(int requestCode);
+ void onShareError(int requestCode, QString message);
+ void onFileUrlReceived(QString url);
+ void onFileReceivedAndSaved(QString url);
+
+public:
+ explicit ShareUtils(QObject *parent = 0);
+ Q_INVOKABLE bool checkMimeTypeView(const QString &mimeType);
+ Q_INVOKABLE bool checkMimeTypeEdit(const QString &mimeType);
+ Q_INVOKABLE void shareText(const QString &text);
+ Q_INVOKABLE void sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
+ Q_INVOKABLE void viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
+ Q_INVOKABLE void editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
+ Q_INVOKABLE void checkPendingIntents(const QString workingDirPath);
+
+private:
+ PlatformShareUtils* mPlatformShareUtils;
+
+};
+
+#endif //SHAREUTILS_H
diff --git a/resources/qml/Components/SelectorPopup.qml b/resources/qml/Components/SelectorPopup.qml
index b598dd5..6cdf282 100644
--- a/resources/qml/Components/SelectorPopup.qml
+++ b/resources/qml/Components/SelectorPopup.qml
@@ -7,7 +7,6 @@ Dialog {
property var dataObj
property string subTitle: ""
- property int implicitY: parent.height - implicitHeight
signal selectionFinished(int index, var data)
diff --git a/resources/qml/Components/SharePopup.qml b/resources/qml/Components/SharePopup.qml
new file mode 100644
index 0000000..be10465
--- /dev/null
+++ b/resources/qml/Components/SharePopup.qml
@@ -0,0 +1,43 @@
+import QtQuick 2.0
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+
+Dialog {
+ id: control
+
+ property string _shareUrl
+
+ parent: Overlay.overlay
+
+ x: (parent.width - width) * 0.5
+ y: (parent.height - height) * 0.5
+
+ modal: true
+
+ title: "Share these results"
+
+ contentItem: RowLayout {
+ Repeater {
+ id: buttonRepeater
+ property var buttons: [
+ ["\uf0c1", "Link", serverConn.shareResultsAsUrl],
+ ["\uf029", "QR-code", null],
+ ["\uf1c1", "Poster", serverConn.shareResultsAsPoster],
+ ]
+
+ model: buttons
+
+ delegate: Button {
+ flat: true
+ font.family: fa5solid.name
+ text: "" + modelData[0] + "
" + modelData[1] + " "
+ onClicked: buttonRepeater.buttons[index][2](_shareUrl)
+ }
+ }
+ }
+
+ function appear(shareUrl) {
+ _shareUrl = shareUrl
+ control.open()
+ }
+}
diff --git a/resources/qml/Pages/WidgetPage.qml b/resources/qml/Pages/WidgetPage.qml
index 651dadd..cd9ac93 100644
--- a/resources/qml/Pages/WidgetPage.qml
+++ b/resources/qml/Pages/WidgetPage.qml
@@ -174,6 +174,19 @@ Page {
return widgetType
}
+ function encodeQueryData(data) {
+ const ret = [];
+ for (let d in data)
+ ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
+ return ret.join('&');
+ }
+
+ function shareWidget() {
+ var url = "https://l.bluerock.dev/?" + encodeQueryData(params)
+ sharePu.appear(url)
+ console.log("Url will be:", url)
+ }
+
Loader {
id: widgetLd
@@ -207,9 +220,6 @@ Page {
delete(widgetLd.sourceComponent)
return false
}
-
-
- //
}
function getFile(widgetType) {
@@ -295,4 +305,10 @@ Page {
}
}
}
+
+ SharePopup {
+ id: sharePu
+
+ Material.theme: root.Material.theme
+ }
}
diff --git a/resources/qml/Widgets/ResultWidget.qml b/resources/qml/Widgets/ResultWidget.qml
index 77e85c4..9a07249 100644
--- a/resources/qml/Widgets/ResultWidget.qml
+++ b/resources/qml/Widgets/ResultWidget.qml
@@ -65,6 +65,15 @@ DataListView {
text: "\uf0e8"
font.family: fa5solid.name
}
+
+ ToolButton {
+ id: shareToolBt
+
+ onClicked: shareWidget()
+
+ text: "\uf1e0"
+ font.family: fa5solid.name
+ }
}
property var widgetData: currentWidgetData
diff --git a/resources/qml/main.qml b/resources/qml/main.qml
index 063b5fa..b60c655 100644
--- a/resources/qml/main.qml
+++ b/resources/qml/main.qml
@@ -132,9 +132,8 @@ Window {
//app.openAthlete() // dorian: 53139 , rustam: 6933 , helen: 53300
//openWidget({nation:'GER'})
//mainStack.push("Pages/AthleteSearchPage.qml")
- //openWidget({comp: 11651, cat: 26})
+ openWidget({comp: 11651, cat: 26})
//openWidget({person: 6623})
- openWidget({cat: 35, comp: 11181, type: "starters"})
}
FontLoader {
diff --git a/resources/qml/qml.qrc b/resources/qml/qml.qrc
index 13a690e..2d4373e 100644
--- a/resources/qml/qml.qrc
+++ b/resources/qml/qml.qrc
@@ -28,5 +28,6 @@
Components/DisclaimerDialog.qml
Components/ColoredItemDelegate.qml
Components/AlignedButton.qml
+ Components/SharePopup.qml
diff --git a/resources/shared/PosterTemplate.png b/resources/shared/PosterTemplate.png
new file mode 100644
index 0000000..74b8721
Binary files /dev/null and b/resources/shared/PosterTemplate.png differ
diff --git a/resources/shared/PosterTemplate.xcf b/resources/shared/PosterTemplate.xcf
new file mode 100644
index 0000000..45fbaae
Binary files /dev/null and b/resources/shared/PosterTemplate.xcf differ
diff --git a/resources/shared/shared.qrc b/resources/shared/shared.qrc
index c4d820c..552fa12 100644
--- a/resources/shared/shared.qrc
+++ b/resources/shared/shared.qrc
@@ -20,5 +20,6 @@
icons/sac-dark.png
fonts/fa5regular.otf
fonts/fa5solid.otf
+ PosterTemplate.png
diff --git a/sources/bluerockbackend.cpp b/sources/bluerockbackend.cpp
index b4f9ef8..c81f6dd 100644
--- a/sources/bluerockbackend.cpp
+++ b/sources/bluerockbackend.cpp
@@ -20,6 +20,8 @@
BlueRockBackend::BlueRockBackend(QObject *parent) : QObject(parent)
{
+ this->_shareUtils = new ShareUtils(this);
+ this->shareResultsAsPoster("test");
}
QVariant BlueRockBackend::getWidgetData(QVariantMap params) {
@@ -79,6 +81,23 @@ QVariantMap BlueRockBackend::getParamsFromUrl(QString stringUrl) {
return params;
}
+void BlueRockBackend::shareResultsAsUrl(QString url) {
+ this->_shareUtils->shareText(url);
+}
+
+void BlueRockBackend::shareResultsAsPoster(QString url) {
+ QPdfWriter writer("/tmp/test.pdf");
+ writer.setPageSize(QPageSize(QPageSize::A4));
+ writer.setPageMargins(QMargins(0, 0, 0, 0));
+ writer.setResolution(600);
+ QPainter painter(&writer);
+ painter.drawText(QRect(0, 0, 1980, 100),Qt::AlignHCenter|Qt::AlignBottom,
+ "Children's Health Checkup Form");
+ QPixmap image(":/PosterTemplate.png");
+ painter.drawPixmap(0,0, writer.width(), writer.height(), image);
+ painter.end();
+}
+
// ------------------------
// --- Helper functions ---
// ------------------------
diff --git a/sources/shareUtils/androidshareutils.cpp b/sources/shareUtils/androidshareutils.cpp
new file mode 100755
index 0000000..8390008
--- /dev/null
+++ b/sources/shareUtils/androidshareutils.cpp
@@ -0,0 +1,348 @@
+// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
+// my blog about Qt for mobile: http://j.mp/qt-x
+// see also /COPYRIGHT and /LICENSE
+
+#include "shareUtils/androidshareutils.h"
+
+#include
+#include
+#include
+
+#include
+#include
+
+const static int RESULT_OK = -1;
+const static int RESULT_CANCELED = 0;
+
+AndroidShareUtils* AndroidShareUtils::mInstance = NULL;
+
+AndroidShareUtils::AndroidShareUtils(QObject* parent) : PlatformShareUtils(parent)
+{
+ // we need the instance for JNI Call
+ mInstance = this;
+}
+
+AndroidShareUtils* AndroidShareUtils::getInstance()
+{
+ if (!mInstance) {
+ mInstance = new AndroidShareUtils;
+ qWarning() << "AndroidShareUtils should be instantiated !";
+ }
+
+ return mInstance;
+}
+
+bool AndroidShareUtils::checkMimeTypeView(const QString &mimeType)
+{
+ QAndroidJniObject jsMime = QAndroidJniObject::fromString(mimeType);
+ jboolean verified = QAndroidJniObject::callStaticMethod("org/ekkescorner/utils/QShareUtils",
+ "checkMimeTypeView",
+ "(Ljava/lang/String;)Z",
+ jsMime.object());
+ qDebug() << "View VERIFIED: " << mimeType << " - " << verified;
+ return verified;
+}
+
+bool AndroidShareUtils::checkMimeTypeEdit(const QString &mimeType)
+{
+ QAndroidJniObject jsMime = QAndroidJniObject::fromString(mimeType);
+ jboolean verified = QAndroidJniObject::callStaticMethod("org/ekkescorner/utils/QShareUtils",
+ "checkMimeTypeEdit",
+ "(Ljava/lang/String;)Z",
+ jsMime.object());
+ qDebug() << "Edit VERIFIED: " << mimeType << " - " << verified;
+ return verified;
+}
+
+void AndroidShareUtils::shareText(const QString &text)
+{
+ QAndroidJniObject jsText = QAndroidJniObject::fromString(text);
+ jboolean ok = QAndroidJniObject::callStaticMethod("org/ekkescorner/utils/QShareUtils",
+ "shareText",
+ "(Ljava/lang/String;)Z",
+ jsText.object());
+
+ if(!ok) {
+ qWarning() << "Unable to resolve activity from Java";
+ emit shareNoAppAvailable(0);
+ }
+}
+
+/*
+ * As default we're going the Java - way with one simple JNI call (recommended)
+ * if altImpl is true we're going the pure JNI way
+ *
+ * If a requestId was set we want to get the Activity Result back (recommended)
+ * We need the Request Id and Result Id to control our workflow
+*/
+void AndroidShareUtils::sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
+{
+ mIsEditMode = false;
+
+ QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
+ QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
+ QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);
+ jboolean ok = QAndroidJniObject::callStaticMethod("org/ekkescorner/utils/QShareUtils",
+ "sendFile",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
+ jsPath.object(), jsTitle.object(), jsMimeType.object(), requestId);
+ if(!ok) {
+ qWarning() << "Unable to resolve activity from Java";
+ emit shareNoAppAvailable(requestId);
+ }
+}
+
+/*
+ * As default we're going the Java - way with one simple JNI call (recommended)
+ * if altImpl is true we're going the pure JNI way
+ *
+ * If a requestId was set we want to get the Activity Result back (recommended)
+ * We need the Request Id and Result Id to control our workflow
+*/
+void AndroidShareUtils::viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
+{
+ mIsEditMode = false;
+
+ QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
+ QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
+ QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);
+ jboolean ok = QAndroidJniObject::callStaticMethod("org/ekkescorner/utils/QShareUtils",
+ "viewFile",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
+ jsPath.object(), jsTitle.object(), jsMimeType.object(), requestId);
+ if(!ok) {
+ qWarning() << "Unable to resolve activity from Java";
+ emit shareNoAppAvailable(requestId);
+ }
+}
+
+/*
+ * As default we're going the Java - way with one simple JNI call (recommended)
+ * if altImpl is true we're going the pure JNI way
+ *
+ * If a requestId was set we want to get the Activity Result back (recommended)
+ * We need the Request Id and Result Id to control our workflow
+*/
+void AndroidShareUtils::editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
+{
+ mIsEditMode = true;
+ mCurrentFilePath = filePath;
+ QFileInfo fileInfo = QFileInfo(mCurrentFilePath);
+ mLastModified = fileInfo.lastModified().toSecsSinceEpoch();
+ qDebug() << "LAST MODIFIED: " << mLastModified;
+
+ QAndroidJniObject jsPath = QAndroidJniObject::fromString(filePath);
+ QAndroidJniObject jsTitle = QAndroidJniObject::fromString(title);
+ QAndroidJniObject jsMimeType = QAndroidJniObject::fromString(mimeType);
+
+ jboolean ok = QAndroidJniObject::callStaticMethod("org/ekkescorner/utils/QShareUtils",
+ "editFile",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
+ jsPath.object(), jsTitle.object(), jsMimeType.object(), requestId);
+
+ if(!ok) {
+ qWarning() << "Unable to resolve activity from Java";
+ emit shareNoAppAvailable(requestId);
+ }
+}
+
+// used from QAndroidActivityResultReceiver
+void AndroidShareUtils::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)
+{
+ Q_UNUSED(data);
+ qDebug() << "From JNI QAndroidActivityResultReceiver: " << receiverRequestCode << "ResultCode:" << resultCode;
+ processActivityResult(receiverRequestCode, resultCode);
+}
+
+// used from Activity.java onActivityResult()
+void AndroidShareUtils::onActivityResult(int requestCode, int resultCode)
+{
+ qDebug() << "From Java Activity onActivityResult: " << requestCode << "ResultCode:" << resultCode;
+ processActivityResult(requestCode, resultCode);
+}
+
+void AndroidShareUtils::processActivityResult(int requestCode, int resultCode)
+{
+ // we're getting RESULT_OK only if edit is done
+ if(resultCode == RESULT_OK) {
+ emit shareEditDone(requestCode);
+ } else if(resultCode == RESULT_CANCELED) {
+ if(mIsEditMode) {
+ // Attention: not all Apps will give you the correct ResultCode:
+ // Google Fotos will send OK if saved and CANCELED if canceled
+ // Some Apps always sends CANCELED even if you modified and Saved the File
+ // so you should check the modified Timestamp of the File to know if
+ // you should emit shareEditDone() or shareFinished() !!!
+ QFileInfo fileInfo = QFileInfo(mCurrentFilePath);
+ qint64 currentModified = fileInfo.lastModified().toSecsSinceEpoch();
+ qDebug() << "CURRENT MODIFIED: " << currentModified;
+ if(currentModified > mLastModified) {
+ emit shareEditDone(requestCode);
+ return;
+ }
+ }
+ emit shareFinished(requestCode);
+ } else {
+ qDebug() << "wrong result code: " << resultCode << " from request: " << requestCode;
+ emit shareError(requestCode, tr("Share: an Error occured"));
+ }
+}
+
+void AndroidShareUtils::checkPendingIntents(const QString workingDirPath)
+{
+ QAndroidJniObject activity = QtAndroid::androidActivity();
+ if(activity.isValid()) {
+ // create a Java String for the Working Dir Path
+ QAndroidJniObject jniWorkingDir = QAndroidJniObject::fromString(workingDirPath);
+ if(!jniWorkingDir.isValid()) {
+ qWarning() << "QAndroidJniObject jniWorkingDir not valid.";
+ emit shareError(0, tr("Share: an Error occured\nWorkingDir not valid"));
+ return;
+ }
+ activity.callMethod("checkPendingIntents","(Ljava/lang/String;)V", jniWorkingDir.object());
+ qDebug() << "checkPendingIntents: " << workingDirPath;
+ return;
+ }
+ qDebug() << "checkPendingIntents: Activity not valid";
+}
+
+void AndroidShareUtils::setFileUrlReceived(const QString &url)
+{
+ if(url.isEmpty()) {
+ qWarning() << "setFileUrlReceived: we got an empty URL";
+ emit shareError(0, tr("Empty URL received"));
+ return;
+ }
+ qDebug() << "AndroidShareUtils setFileUrlReceived: we got the File URL from JAVA: " << url;
+ QString myUrl;
+ if(url.startsWith("file://")) {
+ myUrl= url.right(url.length()-7);
+ qDebug() << "QFile needs this URL: " << myUrl;
+ } else {
+ myUrl= url;
+ }
+
+ // check if File exists
+ QFileInfo fileInfo = QFileInfo(myUrl);
+ if(fileInfo.exists()) {
+ emit fileUrlReceived(myUrl);
+ } else {
+ qDebug() << "setFileUrlReceived: FILE does NOT exist ";
+ emit shareError(0, tr("File does not exist: %1").arg(myUrl));
+ }
+}
+
+void AndroidShareUtils::setFileReceivedAndSaved(const QString &url)
+{
+ if(url.isEmpty()) {
+ qWarning() << "setFileReceivedAndSaved: we got an empty URL";
+ emit shareError(0, tr("Empty URL received"));
+ return;
+ }
+ qDebug() << "AndroidShareUtils setFileReceivedAndSaved: we got the File URL from JAVA: " << url;
+ QString myUrl;
+ if(url.startsWith("file://")) {
+ myUrl= url.right(url.length()-7);
+ qDebug() << "QFile needs this URL: " << myUrl;
+ } else {
+ myUrl= url;
+ }
+
+ // check if File exists
+ QFileInfo fileInfo = QFileInfo(myUrl);
+ if(fileInfo.exists()) {
+ emit fileReceivedAndSaved(myUrl);
+ } else {
+ qDebug() << "setFileReceivedAndSaved: FILE does NOT exist ";
+ emit shareError(0, tr("File does not exist: %1").arg(myUrl));
+ }
+}
+
+// to be safe we check if a File Url from java really exists for Qt
+// if not on the Java side we'll try to read the content as Stream
+bool AndroidShareUtils::checkFileExits(const QString &url)
+{
+ if(url.isEmpty()) {
+ qWarning() << "checkFileExits: we got an empty URL";
+ emit shareError(0, tr("Empty URL received"));
+ return false;
+ }
+ qDebug() << "AndroidShareUtils checkFileExits: we got the File URL from JAVA: " << url;
+ QString myUrl;
+ if(url.startsWith("file://")) {
+ myUrl= url.right(url.length()-7);
+ qDebug() << "QFile needs this URL: " << myUrl;
+ } else {
+ myUrl= url;
+ }
+
+ // check if File exists
+ QFileInfo fileInfo = QFileInfo(myUrl);
+ if(fileInfo.exists()) {
+ qDebug() << "Yep: the File exists for Qt";
+ return true;
+ } else {
+ qDebug() << "Uuups: FILE does NOT exist ";
+ return false;
+ }
+}
+
+// instead of defining all JNICALL as demonstrated below
+// there's another way, making it easier to manage all the methods
+// see https://www.kdab.com/qt-android-episode-5/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT void JNICALL
+Java_org_ekkescorner_examples_sharex_QShareActivity_setFileUrlReceived(JNIEnv *env,
+ jobject obj,
+ jstring url)
+{
+ const char *urlStr = env->GetStringUTFChars(url, NULL);
+ Q_UNUSED (obj)
+ AndroidShareUtils::getInstance()->setFileUrlReceived(urlStr);
+ env->ReleaseStringUTFChars(url, urlStr);
+ return;
+}
+
+JNIEXPORT void JNICALL
+Java_org_ekkescorner_examples_sharex_QShareActivity_setFileReceivedAndSaved(JNIEnv *env,
+ jobject obj,
+ jstring url)
+{
+ const char *urlStr = env->GetStringUTFChars(url, NULL);
+ Q_UNUSED (obj)
+ AndroidShareUtils::getInstance()->setFileReceivedAndSaved(urlStr);
+ env->ReleaseStringUTFChars(url, urlStr);
+ return;
+}
+
+JNIEXPORT bool JNICALL
+Java_org_ekkescorner_examples_sharex_QShareActivity_checkFileExits(JNIEnv *env,
+ jobject obj,
+ jstring url)
+{
+ const char *urlStr = env->GetStringUTFChars(url, NULL);
+ Q_UNUSED (obj)
+ bool exists = AndroidShareUtils::getInstance()->checkFileExits(urlStr);
+ env->ReleaseStringUTFChars(url, urlStr);
+ return exists;
+}
+
+JNIEXPORT void JNICALL
+Java_org_ekkescorner_examples_sharex_QShareActivity_fireActivityResult(JNIEnv *env,
+ jobject obj,
+ jint requestCode,
+ jint resultCode)
+{
+ Q_UNUSED (obj)
+ Q_UNUSED (env)
+ AndroidShareUtils::getInstance()->onActivityResult(requestCode, resultCode);
+ return;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/sources/shareUtils/platformshareutils.cpp b/sources/shareUtils/platformshareutils.cpp
new file mode 100644
index 0000000..c90793a
--- /dev/null
+++ b/sources/shareUtils/platformshareutils.cpp
@@ -0,0 +1,35 @@
+#include "shareUtils/platformshareutils.h"
+
+PlatformShareUtils::PlatformShareUtils(QObject *parent) : QObject(parent)
+{
+
+}
+
+PlatformShareUtils::~PlatformShareUtils() {
+
+}
+
+bool PlatformShareUtils::checkMimeTypeView(const QString &mimeType) {
+ qDebug() << "check view for " << mimeType;
+ return true;
+}
+bool PlatformShareUtils::checkMimeTypeEdit(const QString &mimeType) {
+ qDebug() << "check edit for " << mimeType;
+ return true;
+}
+void PlatformShareUtils::shareText(const QString &text) {
+ qDebug() << text;
+}
+void PlatformShareUtils::sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
+ qDebug() << filePath << " - " << title << "requestId " << requestId << " - " << mimeType << "altImpl? ";
+}
+void PlatformShareUtils::viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
+ qDebug() << filePath << " - " << title << " requestId: " << requestId << " - " << mimeType << "altImpl? ";
+}
+void PlatformShareUtils::editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
+ qDebug() << filePath << " - " << title << " requestId: " << requestId << " - " << mimeType << "altImpl? ";
+}
+
+void PlatformShareUtils::checkPendingIntents(const QString workingDirPath) {
+ qDebug() << "checkPendingIntents " << workingDirPath;
+}
diff --git a/sources/shareUtils/shareutils.cpp b/sources/shareUtils/shareutils.cpp
new file mode 100755
index 0000000..effb0f1
--- /dev/null
+++ b/sources/shareUtils/shareutils.cpp
@@ -0,0 +1,111 @@
+// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
+// my blog about Qt for mobile: http://j.mp/qt-x
+// see also /COPYRIGHT and /LICENSE
+
+#include "shareUtils/shareutils.h"
+
+#ifdef Q_OS_IOS
+#include "cpp/ios/iosshareutils.hpp"
+#endif
+
+#ifdef Q_OS_ANDROID
+#include "shareUtils/androidshareutils.h"
+#endif
+
+ShareUtils::ShareUtils(QObject *parent)
+ : QObject(parent)
+{
+#if defined(Q_OS_IOS)
+ mPlatformShareUtils = new IosShareUtils(this);
+#elif defined(Q_OS_ANDROID)
+ mPlatformShareUtils = new AndroidShareUtils(this);
+#else
+ mPlatformShareUtils = new PlatformShareUtils(this);
+#endif
+
+ bool connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::shareEditDone, this, &ShareUtils::onShareEditDone);
+ Q_ASSERT(connectResult);
+
+ connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::shareFinished, this, &ShareUtils::onShareFinished);
+ Q_ASSERT(connectResult);
+
+ connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::shareNoAppAvailable, this, &ShareUtils::onShareNoAppAvailable);
+ Q_ASSERT(connectResult);
+
+ connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::shareError, this, &ShareUtils::onShareError);
+ Q_ASSERT(connectResult);
+
+ connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::fileUrlReceived, this, &ShareUtils::onFileUrlReceived);
+ Q_ASSERT(connectResult);
+
+ connectResult = connect(mPlatformShareUtils, &PlatformShareUtils::fileReceivedAndSaved, this, &ShareUtils::onFileReceivedAndSaved);
+ Q_ASSERT(connectResult);
+
+ Q_UNUSED(connectResult);
+}
+
+bool ShareUtils::checkMimeTypeView(const QString &mimeType)
+{
+ return mPlatformShareUtils->checkMimeTypeView(mimeType);
+}
+
+bool ShareUtils::checkMimeTypeEdit(const QString &mimeType)
+{
+ return mPlatformShareUtils->checkMimeTypeEdit(mimeType);
+}
+
+void ShareUtils::shareText(const QString &text)
+{
+ mPlatformShareUtils->shareText(text);
+}
+
+void ShareUtils::sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
+{
+ mPlatformShareUtils->sendFile(filePath, title, mimeType, requestId);
+}
+
+void ShareUtils::viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
+{
+ mPlatformShareUtils->viewFile(filePath, title, mimeType, requestId);
+}
+
+void ShareUtils::editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId)
+{
+ mPlatformShareUtils->editFile(filePath, title, mimeType, requestId);
+}
+
+void ShareUtils::checkPendingIntents(const QString workingDirPath)
+{
+ mPlatformShareUtils->checkPendingIntents(workingDirPath);
+}
+
+void ShareUtils::onShareEditDone(int requestCode)
+{
+ emit shareEditDone(requestCode);
+}
+
+void ShareUtils::onShareFinished(int requestCode)
+{
+ emit shareFinished(requestCode);
+}
+
+void ShareUtils::onShareNoAppAvailable(int requestCode)
+{
+ emit shareNoAppAvailable(requestCode);
+}
+
+void ShareUtils::onShareError(int requestCode, QString message)
+{
+ emit shareError(requestCode, message);
+}
+
+void ShareUtils::onFileUrlReceived(QString url)
+{
+ emit fileUrlReceived(url);
+}
+
+void ShareUtils::onFileReceivedAndSaved(QString url)
+{
+ emit fileReceivedAndSaved(url);
+}
+