diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..7aa25bb
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "qzxing"]
+ path = qzxing
+ url = https://github.com/ftylitak/qzxing.git
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 06d2788..5712cc1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+# [0.6.1] - 2021-10-20
+### Added
+- Privacy policy link to comply with google play policies
+
+# [0.6.0] - 2021-08-07
+### Changed
+- The subtitle in results and startlists is now the route name instead of the category name
+
+### Added
+- Dark mode
+- QR-Code scanning
+- Sharing of every view using either link, QR-Code or a poster
+- Text which is too large too fit is scrollable now in most places
+- German translations
+- URL handler for https://l.bluerock.dev and https://app.bluerock.dev
+
+### Fixed
+- Rare issue with missing background in boulder result rect
+
# [0.5.1] - 2021-07-06
### Fixed
- In-app purchase
@@ -20,7 +39,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- the calendar now scrolls less far down
- improoved layout in landscape mode
- some design changes in profile page and speed flowchart
-- added second link for "further infos" in calendar
+
+### Added
+- Second link for "further infos" in calendar
# [0.03.0] - 2019-07-11
### Added
diff --git a/README.md b/README.md
index 4aa83b8..932984f 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,20 @@
# Digital Rock ranking
-App to view ranking and calendar data from https://www.digitalrock.de/
\ No newline at end of file
+App to view ranking and calendar data from https://www.digitalrock.de/
+
+# Init
+´´´
+git submodule init
+git submodule update
+´´´
+
+# Poster
+The poster offsets are (always top left of the element):
+- Width: 1654
+- Height: 2339
+### QR-Code
+- Cooridnates: 414, 414
+- Size: 1650x1650
+### Comp name
+- Cooridnates: x: 324, y: 2500
+- Size: 64 per line; 1835 width; 150 max height
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index ec293df..9f1c607 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -10,7 +10,7 @@
-
+
@@ -68,9 +68,23 @@
-->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/build.gradle b/android/build.gradle
index 0051ff0..7ef94c3 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 {
@@ -72,10 +73,11 @@ android {
defaultConfig {
resConfig "en"
minSdkVersion = qtMinSdkVersion
- targetSdkVersion = 29
+ targetSdkVersion = qtTargetSdkVersion
}
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..6a45457
--- /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..c070a26
--- /dev/null
+++ b/android/src/de/itsblue/blueROCK/MainActivity.java
@@ -0,0 +1,232 @@
+// (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);
+ //
+ public static native void setOtherUrlReceived(String url, String scheme);
+ // 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) {
+ super.onActivityResult(requestCode, resultCode, 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);
+ setOtherUrlReceived(intentUri.toString(), 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 e4ff89f..a6949d2 100644
--- a/blueROCK.pro
+++ b/blueROCK.pro
@@ -1,7 +1,7 @@
-QT += quick qml quickcontrols2 purchasing widgets
+QT += quick qml quickcontrols2 purchasing
CONFIG += c++11
-VERSION = 0.5.1
+VERSION = 0.6.1
TARGET = blueROCK
# The following define makes your compiler emit warnings if you use
@@ -15,16 +15,30 @@ 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/main.cpp \
- sources/serverconn.cpp
+ sources/bluerockbackend.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 \
- #resources/shared/icons/bluerock/index.theme \
- #$$files(resources/shared/icons/*.png, true)
+ resources/translations/translations.qrc
+TRANSLATIONS += resources/translations/en.ts \
+ resources/translations/de.ts
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
@@ -37,27 +51,29 @@ 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/serverconn.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
+ README.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)"
@@ -73,17 +89,63 @@ android {
ANDROID_VERSION_NAME = $$VERSION
ANDROID_VERSION_CODE = $$droidVersionCode($$ANDROID_VERSION_NAME)
- message(Android version code: $$ANDROID_VERSION_CODE)
+ ANDROID_TARGET_SDK_VERSION = 29
include(/home/dorian/Android/Sdk/android_openssl/openssl.pri)
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
}
ios {
+ OBJECTIVE_SOURCES += sources/shareUtils/ios/iosshareutils.mm \
+ sources/iospermissionutils.mm \
+ sources/shareUtils/ios/docviewcontroller.mm
+
+ HEADERS += headers/shareUtils/ios/iosshareutils.h \
+ headers/iospermissionutils.h \
+ headers/shareUtils/ios/docviewcontroller.h
+
+ OTHER_FILES += ios/Info.plist \
+ ios/blueROCK.entitlements
+
+ QMAKE_INFO_PLIST = ios/Info.plist
+
+
+ #QMAKE_IOS_DEPLOYMENT_TARGET = 12.0
+
+ #disable_warning.name = GCC_WARN_64_TO_32_BIT_CONVERSION
+ #disable_warning.value = NO
+ #QMAKE_MAC_XCODE_SETTINGS += disable_warning
+
+ # see https://bugreports.qt.io/browse/QTCREATORBUG-16968
+ # ios_signature.pri not part of project repo because of private signature details
+ # contains:
+ # QMAKE_XCODE_CODE_SIGN_IDENTITY = "iPhone Developer"
+ # MY_DEVELOPMENT_TEAM.name = DEVELOPMENT_TEAM
+ # MY_DEVELOPMENT_TEAM.value = your team Id from Apple Developer Account
+ # QMAKE_MAC_XCODE_SETTINGS += MY_DEVELOPMENT_TEAM
+
+ #include(ios_signature.pri)
+
QMAKE_ASSET_CATALOGS += resources/shared/Assets.xcassets
+
+ MY_ENTITLEMENTS.name = CODE_SIGN_ENTITLEMENTS
+ MY_ENTITLEMENTS.value = $$PWD/ios/blueROCK.entitlements
+ QMAKE_MAC_XCODE_SETTINGS += MY_ENTITLEMENTS
+
+ MY_BUNDLE_ID.name = PRODUCT_BUNDLE_IDENTIFIER
+ MY_BUNDLE_ID.value = de.itsblue.bluerock
+ QMAKE_MAC_XCODE_SETTINGS += MY_BUNDLE_ID
xcode_product_bundle_identifier_setting.value = "de.itsblue.bluerock"
+ PRODUCT_IDENTIFIER = de.itsblue.bluerock
}
+CONFIG += enable_decoder_qr_code \
+ enable_encoder_qr_code \
+ qzxing_multimedia \
+ qzxing_qml
+
+include(qzxing/src/QZXing-components.pri)
+
# this has to be the last line!
ANDROID_ABIS = armeabi-v7a arm64-v8a
diff --git a/headers/appsettings.h b/headers/appsettings.h
index 9b92aba..13b502f 100644
--- a/headers/appsettings.h
+++ b/headers/appsettings.h
@@ -21,12 +21,6 @@ private:
QSettings *settingsManager;
// QSettings object which cares about our settings.ini file
- QSettings *themeSettingsManager;
- // QSettings object which cares about the themes
-
-signals:
- void themeChanged();
-
public slots:
Q_INVOKABLE QString read(const QString &key);
// function to read values from the settings file
diff --git a/headers/bluerockbackend.h b/headers/bluerockbackend.h
new file mode 100644
index 0000000..b9886e3
--- /dev/null
+++ b/headers/bluerockbackend.h
@@ -0,0 +1,81 @@
+/*
+ blueROCK - for digital rock
+ Copyright (C) 2019 Dorian Zedler
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef SERVERCONN_H
+#define SERVERCONN_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "QZXing.h"
+
+#ifdef Q_OS_ANDROID
+#include
+#elif defined Q_OS_IOS
+#include "iospermissionutils.h"
+#endif
+
+#include "shareUtils/shareutils.h"
+
+class BlueRockBackend : public QObject
+{
+ Q_OBJECT
+public:
+ explicit BlueRockBackend(QObject *parent = nullptr);
+
+private:
+ QVariantMap _senddata(QUrl serviceUrl, QUrlQuery pdata = QUrlQuery());
+
+ ShareUtils* _shareUtils;
+#ifdef Q_OS_IOS
+ IosPermissionUtils* _iosPermissionUtils;
+#endif
+ const QStringList _validBaseDomains = {"digitalrock.de", "bluerock.dev"};
+ bool _pendingIntentsChecked;
+
+signals:
+ Q_INVOKABLE void openedViaUrl(QString url, QString scheme);
+
+public slots:
+
+ Q_INVOKABLE QVariant getWidgetData(QVariantMap params);
+ Q_INVOKABLE QVariantMap getParamsFromUrl(QString url);
+ Q_INVOKABLE void shareResultsAsUrl(QString url, QString compName);
+ Q_INVOKABLE void shareResultsAsPoster(QString url, QString compName);
+
+ Q_INVOKABLE bool isCameraPermissionGranted();
+ Q_INVOKABLE bool requestCameraPermission();
+
+#if defined(Q_OS_ANDROID)
+ void onApplicationStateChanged(Qt::ApplicationState applicationState);
+#endif
+
+};
+
+#endif // SERVERCONN_H
diff --git a/headers/iospermissionutils.h b/headers/iospermissionutils.h
new file mode 100644
index 0000000..132e60f
--- /dev/null
+++ b/headers/iospermissionutils.h
@@ -0,0 +1,22 @@
+#ifndef IOSPERMISSIONUTILS_H
+#define IOSPERMISSIONUTILS_H
+
+#include
+#include
+
+class IosPermissionUtils : QObject
+{
+ Q_OBJECT
+public:
+ IosPermissionUtils();
+ bool isCameraPermissionGranted();
+ bool requestCameraPermission();
+
+private:
+ QEventLoop* _responseWaitLoop;
+
+signals:
+ void permissionRequestFinished(bool result);
+};
+
+#endif // IOSPERMISSIONUTILS_H
diff --git a/headers/serverconn.h b/headers/serverconn.h
deleted file mode 100644
index 2630af3..0000000
--- a/headers/serverconn.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- blueROCK - for digital rock
- Copyright (C) 2019 Dorian Zedler
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
-
-#ifndef SERVERCONN_H
-#define SERVERCONN_H
-
-#include
-#include
-#include
-#include
-#include
-
-class ServerConn : public QObject
-{
- Q_OBJECT
-public:
- explicit ServerConn(QObject *parent = nullptr);
-
-private:
- QVariantMap senddata(QUrl serviceUrl, QUrlQuery pdata = QUrlQuery());
-
-signals:
-
-public slots:
-
- QVariant getWidgetData(QVariantMap params);
-
-};
-
-#endif // SERVERCONN_H
diff --git a/headers/shareUtils/androidshareutils.h b/headers/shareUtils/androidshareutils.h
new file mode 100755
index 0000000..cbb30b0
--- /dev/null
+++ b/headers/shareUtils/androidshareutils.h
@@ -0,0 +1,51 @@
+// (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
+{
+ Q_OBJECT
+public:
+ AndroidShareUtils(QObject* parent = nullptr);
+ bool checkMimeTypeView(const QString &mimeType) override;
+ bool checkMimeTypeEdit(const QString &mimeType) override;
+ virtual QString getTemporaryFileLocationPath() override;
+ void shareText(const QString &text, const QUrl &url) 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 setOtherUrlReceived(const QString &url, const QString &scheme);
+ 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/ios/docviewcontroller.h b/headers/shareUtils/ios/docviewcontroller.h
new file mode 100644
index 0000000..b70867b
--- /dev/null
+++ b/headers/shareUtils/ios/docviewcontroller.h
@@ -0,0 +1,21 @@
+// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
+// my blog about Qt for mobile: http://j.mp/qt-x
+// see also /COPYRIGHT and /LICENSE
+
+#ifndef DOCVIEWCONTROLLER_HPP
+#define DOCVIEWCONTROLLER_HPP
+
+#import
+#import "iosshareutils.h"
+
+@interface DocViewController : UIViewController
+
+@property int requestId;
+
+@property IosShareUtils *mIosShareUtils;
+
+@end
+
+
+
+#endif // DOCVIEWCONTROLLER_HPP
diff --git a/headers/shareUtils/ios/iosshareutils.h b/headers/shareUtils/ios/iosshareutils.h
new file mode 100755
index 0000000..55d7bf7
--- /dev/null
+++ b/headers/shareUtils/ios/iosshareutils.h
@@ -0,0 +1,31 @@
+// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
+// my blog about Qt for mobile: http://j.mp/qt-x
+// see also /COPYRIGHT and /LICENSE
+
+#ifndef __IOSSHAREUTILS_H__
+#define __IOSSHAREUTILS_H__
+
+#include "headers/shareUtils/platformshareutils.h"
+
+class IosShareUtils : public PlatformShareUtils
+{
+ Q_OBJECT
+
+public:
+ explicit IosShareUtils(QObject *parent = 0);
+ bool checkMimeTypeView(const QString &mimeType) override;
+ bool checkMimeTypeEdit(const QString &mimeType) override;
+ void shareText(const QString &text, const QUrl &url) 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 handleDocumentPreviewDone(const int &requestId);
+
+public slots:
+ void handleFileUrlReceived(const QUrl &url);
+ void handleHttpsUrlReceived(const QUrl &url);
+
+};
+
+#endif
diff --git a/headers/shareUtils/platformshareutils.h b/headers/shareUtils/platformshareutils.h
new file mode 100644
index 0000000..99781e1
--- /dev/null
+++ b/headers/shareUtils/platformshareutils.h
@@ -0,0 +1,51 @@
+// (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
+#include
+#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 otherUrlReceived(QString url, QString scheme);
+ void fileReceivedAndSaved(QString url);
+
+public:
+ PlatformShareUtils(QObject *parent = 0);
+ virtual ~PlatformShareUtils();
+ virtual bool checkMimeTypeView(const QString &mimeType);
+ virtual bool checkMimeTypeEdit(const QString &mimeType);
+ virtual QString getTemporaryFileLocationPath();
+ virtual void shareText(const QString &text, const QUrl &url);
+ 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..750ce73
--- /dev/null
+++ b/headers/shareUtils/shareutils.h
@@ -0,0 +1,63 @@
+// (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 otherUrlReceived(QString url, QString scheme);
+ 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 onOtherUrlReceived(QString url, QString scheme);
+ 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 QString getTemporaryFileLocationPath();
+ Q_INVOKABLE void shareText(const QString &text, const QUrl &url);
+ 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/ios/Info.plist b/ios/Info.plist
new file mode 100644
index 0000000..b38762e
--- /dev/null
+++ b/ios/Info.plist
@@ -0,0 +1,45 @@
+
+
+
+
+ CFBundleDisplayName
+ ${PRODUCT_NAME}
+ CFBundleExecutable
+ ${EXECUTABLE_NAME}
+ CFBundleIconFile
+ ${ASSETCATALOG_COMPILER_APPICON_NAME}
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleName
+ ${PRODUCT_NAME}
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ ${QMAKE_SHORT_VERSION}
+ CFBundleSignature
+ ${QMAKE_PKGINFO_TYPEINFO}
+ CFBundleVersion
+ ${QMAKE_FULL_VERSION}
+ LSApplicationQueriesSchemes
+
+ https
+
+ LSRequiresIPhoneOS
+
+ MinimumOSVersion
+ ${IPHONEOS_DEPLOYMENT_TARGET}
+ NOTE
+ This file was generated by Qt/QMake.
+ NSCameraUsageDescription
+ blueROCK would like to access the camera.
+ UILaunchStoryboardName
+ LaunchScreen
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/ios/blueROCK.entitlements b/ios/blueROCK.entitlements
new file mode 100644
index 0000000..9981ec4
--- /dev/null
+++ b/ios/blueROCK.entitlements
@@ -0,0 +1,11 @@
+
+
+
+
+ com.apple.developer.associated-domains
+
+ applinks:l.bluerock.dev
+ applinks:app.bluerock.dev
+
+
+
diff --git a/qzxing b/qzxing
new file mode 160000
index 0000000..cfc7285
--- /dev/null
+++ b/qzxing
@@ -0,0 +1 @@
+Subproject commit cfc728583b867e157bd27e8b7c239c05a081e562
diff --git a/resources/qml/Components/AlignedButton.qml b/resources/qml/Components/AlignedButton.qml
new file mode 100644
index 0000000..2d0a2ee
--- /dev/null
+++ b/resources/qml/Components/AlignedButton.qml
@@ -0,0 +1,21 @@
+import QtQuick 2.0
+import QtQuick.Controls 2.12
+import QtQuick.Controls.Material 2.12
+
+Button {
+ id: control
+
+ property alias horizontalAlignment: label.horizontalAlignment
+ property alias verticalAlignment: label.verticalAlignment
+
+ contentItem: Label {
+ id: label
+
+ text: control.text
+ font: control.font
+
+ color: !control.enabled ? control.Material.hintTextColor :
+ control.flat && control.highlighted ? control.Material.accentColor :
+ control.highlighted ? control.Material.primaryHighlightedTextColor : control.Material.foreground
+ }
+}
diff --git a/resources/qml/Components/AppToolBar.qml b/resources/qml/Components/AppToolBar.qml
index e0bfa87..12b64bc 100644
--- a/resources/qml/Components/AppToolBar.qml
+++ b/resources/qml/Components/AppToolBar.qml
@@ -37,7 +37,7 @@ Item {
Rectangle {
id: toolBar
- color: "white"
+ color: Material.background
anchors.fill: parent
Rectangle {
diff --git a/resources/qml/Components/ColoredItemDelegate.qml b/resources/qml/Components/ColoredItemDelegate.qml
new file mode 100644
index 0000000..9c5ccdf
--- /dev/null
+++ b/resources/qml/Components/ColoredItemDelegate.qml
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Controls.Material 2.15
+import QtQuick.Controls.Material.impl 2.15
+
+ItemDelegate {
+ id: control
+
+ Material.background: "transparent"
+
+ background: Rectangle {
+ implicitHeight: control.Material.delegateHeight
+
+ color: control.highlighted ? control.Material.listHighlightColor : control.Material.background
+
+ Ripple {
+ width: parent.width
+ height: parent.height
+
+ clip: visible
+ pressed: control.pressed
+ anchor: control
+ active: control.down || control.visualFocus || control.hovered
+ color: control.Material.rippleColor
+ }
+ }
+}
diff --git a/resources/qml/Components/CompetitionCalendarDelegate.qml b/resources/qml/Components/CompetitionCalendarDelegate.qml
index 4d108ff..501cfed 100644
--- a/resources/qml/Components/CompetitionCalendarDelegate.qml
+++ b/resources/qml/Components/CompetitionCalendarDelegate.qml
@@ -1,8 +1,12 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
+import QtQuick.Controls.Material 2.12
+import QtQuick.Templates 2.12 as T
+import QtQuick.Controls.impl 2.12
+import QtQuick.Controls.Material.impl 2.12
-ItemDelegate {
+ColoredItemDelegate {
id: competitionDel
property bool over
@@ -81,15 +85,7 @@ ItemDelegate {
NumberAnimation { target: competitionDel; property: "scale"; from: 1; to: 0.8; duration: 400 }
}
- Rectangle {
- id: delBackroundRect
-
- anchors.fill: parent
-
- opacity: 0.5
-
- color: control.getCompCatData(catId) === undefined ? "white":control.getCompCatData(catId)["bgcolor"]
- }
+ Material.background: control.getCompCatData(catId) === undefined ? app.federalColor:control.getCompCatData(catId)["bgcolor"]
Column {
id: compDelCol
@@ -117,14 +113,17 @@ ItemDelegate {
ToolButton {
id: bookmarkTb
- icon.name: competitionDel.thisIsFavored ? "pinFilled":"pin"
+
+ text: "\uf005"
+ font.family: competitionDel.thisIsFavored ? fa5solid.name : fa5regular.name
+
onClicked: {
control.editFavorites(!competitionDel.thisIsFavored, parseInt(thisData['WetId']))
}
Layout.alignment: Layout.Right
- Behavior on icon.name {
+ Behavior on font.family {
SequentialAnimation {
NumberAnimation {
property: "scale"
@@ -146,8 +145,6 @@ ItemDelegate {
Label {
id: dateLa
- color: "grey"
-
text: date
}
}
diff --git a/resources/qml/Components/DataListView.qml b/resources/qml/Components/DataListView.qml
index 38f7c7a..edc4270 100644
--- a/resources/qml/Components/DataListView.qml
+++ b/resources/qml/Components/DataListView.qml
@@ -64,21 +64,6 @@ ListView {
}
}
- InfoArea {
- id: infoArea
-
- anchors {
- left: control.left
- right: control.right
- top: control.top
- margins: app.landscape() ? control.width * 0.4:control.width * 0.3
- topMargin: control.height*( status === 901 ? 0.6:0.5) - height * 0.8
- }
-
- excludedCodes: [200, 902, 905]
- errorCode: control.status
- }
-
PullRefresher {
target: control
diff --git a/resources/qml/Components/DisclaimerDialog.qml b/resources/qml/Components/DisclaimerDialog.qml
index c96c3cf..71b172c 100644
--- a/resources/qml/Components/DisclaimerDialog.qml
+++ b/resources/qml/Components/DisclaimerDialog.qml
@@ -13,8 +13,15 @@ Dialog {
x: (parent.width - width) * 0.5
y: (parent.height - height) * 0.5
- width: parent.width * 0.8
- height: implicitHeight
+ implicitWidth: parent.width * 0.9
+
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ contentHeight + topPadding + bottomPadding
+ + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
+ + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
+
+ //width: app.width * 0.8
+ //height: implicitHeight
modal: true
@@ -41,8 +48,7 @@ Dialog {
contentItem: Label {
wrapMode: Text.Wrap
- width: control.width * 0.8
- height: implicitHeight
+ width: control.parent * 0.8
text: control.content
diff --git a/resources/qml/Components/FancyButton.qml b/resources/qml/Components/FancyButton.qml
index 69b53ef..3ea03d0 100644
--- a/resources/qml/Components/FancyButton.qml
+++ b/resources/qml/Components/FancyButton.qml
@@ -19,8 +19,10 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtGraphicalEffects 1.0
+import QtQuick.Controls.Material 2.12
+import QtQuick.Controls.Material.impl 2.12
-ToolButton {
+MouseArea {
id: control
property string image
@@ -39,7 +41,7 @@ ToolButton {
}
}
- contentItem: Item {
+ Item {
id: controlBackgroundContainer
anchors.fill: parent
@@ -67,7 +69,7 @@ ToolButton {
radius: height * 0.2
- color: control.down ? Qt.darker(control.backgroundColor, 1.03) : control.backgroundColor
+ color: Material.dialogColor //control.down ? Qt.darker(control.backgroundColor, 1.03) : control.backgroundColor
Image {
id: buttonIcon
@@ -84,22 +86,30 @@ ToolButton {
scale: control.imageScale
}
- Behavior on color {
- ColorAnimation {
- duration: 100
+ Ripple {
+ id: ripple
+ visible: true
+ clipRadius: controlBackground.radius
+ clip: true
+ width: parent.width
+ height: parent.height
+ pressed: control.pressed
+ anchor: control
+ active: control.pressed || control.visualFocus || control.containsMouse
+ color: control.Material.rippleColor
+
+ layer.enabled: true
+ layer.effect: OpacityMask {
+ maskSource: Item {
+ width: ripple.width
+ height: ripple.height
+ Rectangle {
+ anchors.fill: parent
+ radius: controlBackground.radius
+ }
+ }
}
}
}
}
-
- Text {
- id: conetntText
- text: qsTr(control.text)
- anchors.centerIn: parent
- font: parent.font
- color: control.textColor
- opacity: control.enabled ? 1:0.4
- }
-
-
}
diff --git a/resources/qml/Components/MovingLabel.qml b/resources/qml/Components/MovingLabel.qml
new file mode 100644
index 0000000..67b1ad8
--- /dev/null
+++ b/resources/qml/Components/MovingLabel.qml
@@ -0,0 +1,90 @@
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Controls.Material 2.12
+
+Item {
+ id: control
+
+ property alias text: firstLabel.text
+ property alias font: firstLabel.font
+ property alias verticalAlignment: firstLabel.verticalAlignment
+ property int spacing: 30
+
+ property MovingLabel syncWithLabel
+ property int _spacing: syncWithLabel && syncWithLabel._labelWidth > _labelWidth ? (syncWithLabel._labelWidth - _labelWidth + syncWithLabel.spacing) : spacing
+ property alias _labelWidth: firstLabel.width
+
+ signal linkActivated(string link)
+
+ clip: true
+ height: firstLabel.height
+
+ onTextChanged: {
+ _resetScroll()
+ if(control.syncWithLabel)
+ control.syncWithLabel._resetScroll()
+ }
+
+ function startScroll(triggerSyncedLabel=true) {
+ if(control.syncWithLabel && triggerSyncedLabel)
+ control.syncWithLabel.startScroll(false)
+ if(control.width < firstLabel.width && !scrollAnimation.running)
+ scrollAnimation.start()
+ }
+
+ function _resetScroll() {
+ scrollAnimation.stop()
+ firstLabel.anchors.leftMargin = 0
+ }
+
+ Label {
+ id: firstLabel
+
+ anchors {
+ left: parent.left
+ verticalCenter: parent.verticalCenter
+ }
+
+ onLinkActivated: control.onLinkActivated(link)
+ }
+
+ Label {
+ id: secondLabel
+
+ anchors {
+ left: firstLabel.right
+ leftMargin: control._spacing
+ verticalCenter: firstLabel.verticalCenter
+ }
+
+ visible: scrollAnimation.running
+
+ font: firstLabel.font
+ text: firstLabel.text
+ verticalAlignment: firstLabel.verticalAlignment
+ elide: firstLabel.elide
+ bottomPadding: firstLabel.bottomPadding
+ padding: firstLabel.padding
+
+ onLinkActivated: control.onLinkActivated(link)
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: control.startScroll()
+ }
+
+ NumberAnimation {
+ id: scrollAnimation
+ target: firstLabel
+ property: "anchors.leftMargin"
+ from: 0
+ to: -(firstLabel.width + control._spacing)
+ duration: (to / -100) * 1500
+
+ onRunningChanged: {
+ if(!running)
+ control._resetScroll()
+ }
+ }
+}
diff --git a/resources/qml/Components/PullRefresher.qml b/resources/qml/Components/PullRefresher.qml
index 9004143..781106a 100644
--- a/resources/qml/Components/PullRefresher.qml
+++ b/resources/qml/Components/PullRefresher.qml
@@ -18,6 +18,7 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
+import QtQuick.Controls.Material 2.12
import QtGraphicalEffects 1.0
Item {
@@ -37,10 +38,6 @@ Item {
property double dragRefreshPositionMultiplier: 0.6 // position of the item when starting to refresh
- property color backgroundColor: "white" // color for the pre-defined background
- property color pullIndicatorColor: "black" // color for the pre-defined pull indicator
- //property color busyIndicatorColor: "pink" // color for the pre-defined busy indicator
-
readonly property double dragProgress: Math.min( userPosition / dragOutPosition, 1)
property Component background: Item {
@@ -61,7 +58,7 @@ Item {
anchors.fill: parent
radius: width * 0.5
- color: control.backgroundColor
+ color: Material.dialogColor
}
}
property Component busyIndicator: BusyIndicator { running: true }
@@ -107,7 +104,7 @@ Item {
ctx.reset()
ctx.lineWidth = lineWidth;
- ctx.strokeStyle = control.pullIndicatorColor;
+ ctx.strokeStyle = control.Material.foreground;
// middle line
ctx.moveTo(width/2, topMargin);
diff --git a/resources/qml/Components/ResultDelegate.qml b/resources/qml/Components/ResultDelegate.qml
index 29a1df5..e0d4df3 100644
--- a/resources/qml/Components/ResultDelegate.qml
+++ b/resources/qml/Components/ResultDelegate.qml
@@ -1,12 +1,13 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
+import QtQuick.Controls.Material 2.15
-ItemDelegate {
+ColoredItemDelegate {
id: partDel
- property int ind: index
- property var thisData: widgetData[ "participants" ][partDel.ind]
+ property int thisIndex: index
+ property var thisData: widgetData[ "participants" ][partDel.thisIndex]
width: control.width
height: partDelCol.showSideBySide ? 40:70
@@ -24,23 +25,14 @@ ItemDelegate {
app.openWidget({person:thisData["PerId"]})
}
+ highlighted: partDel.thisIndex % 2 == 0
+
ParallelAnimation {
id: fadeInPa
NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 }
NumberAnimation { target: partDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 }
}
- Rectangle {
- id: partDelBackgroundRect
- anchors.fill: parent
-
- width: partDel.width
-
- color: partDel.ind % 2 == 0 ? "white":"lightgrey"
-
- opacity: 0.2
- }
-
GridLayout {
id: partDelCol
@@ -133,8 +125,8 @@ ItemDelegate {
function getDataForIcon(index){
// TODO: clean
- var resultString = widgetData[ "participants" ][partDel.ind]["boulder"+(index+1)]
- var numTrys = widgetData[ "participants" ][partDel.ind]["try"+(index+1)]
+ var resultString = widgetData[ "participants" ][partDel.thisIndex]["boulder"+(index+1)]
+ var numTrys = widgetData[ "participants" ][partDel.thisIndex]["try"+(index+1)]
var resultList = []
@@ -222,7 +214,7 @@ ItemDelegate {
context.arc(radius + offsetX, radius + offsetY, radius, Math.PI, 1.5 * Math.PI);
// fill
- if(resultData[0] !== -1) {
+ if(resultData[0] !== -1 || resultData[2] !== -1) {
// if there is a result available -> draw background
context.fillStyle = "#b7b7b7";
}
@@ -234,7 +226,7 @@ ItemDelegate {
// outline
context.lineWidth = 1;
- context.strokeStyle = '#424242';
+ context.strokeStyle = Material.primaryTextColor;
context.stroke();
if(resultData[1] > 0){
@@ -262,7 +254,7 @@ ItemDelegate {
// outline
context.lineWidth = 1;
- context.strokeStyle = '#424242';
+ context.strokeStyle = Material.primaryTextColor;
context.stroke();
@@ -287,7 +279,7 @@ ItemDelegate {
// outline
context.lineWidth = 1;
- context.strokeStyle = '#424242';
+ context.strokeStyle = Material.primaryTextColor;
context.stroke();
}
}
@@ -311,6 +303,8 @@ ItemDelegate {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
+ color: "#dd000000"
+
text: boulderResCv.resultData[2]
}
@@ -336,6 +330,8 @@ ItemDelegate {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
+ color: "#dd000000"
+
text: boulderResCv.resultData[1]
}
@@ -360,6 +356,8 @@ ItemDelegate {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
+ color: "#dd000000"
+
text: boulderResCv.resultData[0]
}
}
@@ -427,7 +425,7 @@ ItemDelegate {
visible: index === 0
- color: "grey"
+ color: Material.primaryTextColor
}
Rectangle {
@@ -438,7 +436,7 @@ ItemDelegate {
width: 1
height: parent.height
- color: "grey"
+ color: Material.primaryTextColor
}
Label {
@@ -454,7 +452,9 @@ ItemDelegate {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
- text: widgetData[ "participants" ][partDel.ind]["result"+(generalResRep.routes[index][0])] === undefined ? "":widgetData[ "participants" ][partDel.ind]["result"+(generalResRep.routes[index][0])]
+ text: widgetData["participants"][partDel.thisIndex]["result"+(generalResRep.routes[index][0])] === undefined ?
+ "":
+ widgetData[ "participants" ][partDel.thisIndex]["result"+(generalResRep.routes[index][0])]
}
}
@@ -479,7 +479,7 @@ ItemDelegate {
font.pixelSize: Math.abs( height * 0.6 )
minimumPixelSize: 1
- text: widgetData[ "participants" ][partDel.ind]["result"] === undefined ? "":widgetData[ "participants" ][partDel.ind]["result"]
+ text: widgetData[ "participants" ][partDel.thisIndex]["result"] === undefined ? "":widgetData[ "participants" ][partDel.thisIndex]["result"]
}
}
}
diff --git a/resources/qml/Components/SelectorPopup.qml b/resources/qml/Components/SelectorPopup.qml
index f9dcd4d..356441e 100644
--- a/resources/qml/Components/SelectorPopup.qml
+++ b/resources/qml/Components/SelectorPopup.qml
@@ -1,5 +1,5 @@
-import QtQuick 2.9
-import QtQuick.Controls 2.4
+import QtQuick 2.15
+import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.3
Dialog {
@@ -7,9 +7,9 @@ Dialog {
property var dataObj
property string subTitle: ""
- property int implicitY: parent.height - implicitHeight
signal selectionFinished(int index, var data)
+ signal linkActivated(string link)
parent: Overlay.overlay
@@ -33,38 +33,35 @@ Dialog {
id: selectorPuHeaderCol
width: control.width
- height: headerSubLa.text !== "" && headerLa.text !== "" ? 73 : 40
+ height: headerLa.height + headerTopSpacerItm.height + (control.subTitle ? headerSubLa.height:0)
- Label {
+ Item {
+ id: headerTopSpacerItm
+ height: control.padding
+ width: parent.width
+ }
+
+ MovingLabel {
id: headerLa
- visible: control.title
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: selectorPuHeaderCol.width - control.padding * 2
- width: selectorPuHeaderCol.width
-
- elide: "ElideRight"
- padding: control.padding
- bottomPadding: 0
font.bold: true
font.pixelSize: 16
text: control.title
- onLinkActivated: {
- console.log("Opening " + link)
- Qt.openUrlExternally(link)
- }
+ onLinkActivated: control.linkActivated(link)
}
Label {
id: headerSubLa
- visible: control.subTitle
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: selectorPuHeaderCol.width - control.padding * 2
- width: selectorPuHeaderCol.width
-
- elide: "ElideRight"
- padding: control.padding
+ wrapMode: Text.Wrap
topPadding: 5
bottomPadding: 0
font.bold: true
@@ -73,8 +70,7 @@ Dialog {
text: control.subTitle
onLinkActivated: {
- console.log("Opening " + link)
- Qt.openUrlExternally(link)
+ control.linkActivated(link)
}
}
@@ -83,6 +79,7 @@ Dialog {
background: Item {
Rectangle {
id: backgroundRect
+
anchors {
fill: parent
bottomMargin: -radius
diff --git a/resources/qml/Components/SharePopup.qml b/resources/qml/Components/SharePopup.qml
new file mode 100644
index 0000000..4a512df
--- /dev/null
+++ b/resources/qml/Components/SharePopup.qml
@@ -0,0 +1,124 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import QZXing 3.1
+import QtGraphicalEffects 1.0
+
+Dialog {
+ id: control
+
+ property string _shareUrl
+ property string _compName
+
+ parent: Overlay.overlay
+
+ x: (parent.width - width) * 0.5
+ y: (parent.height - height) * 0.5
+
+ modal: true
+ //% "Share these results"
+ title: qsTrId("#shareResultsHeadline")
+
+ onClosed: {
+ shareComponentLoader.sourceComponent = null
+ }
+
+ contentItem: Loader {
+ id: shareComponentLoader
+
+ asynchronous: false
+ sourceComponent: null
+ }
+
+ Component {
+ id: shareComponent
+ StackLayout {
+ id: stackLayout
+ currentIndex: 0
+
+ RowLayout {
+ id: menuRow
+ Repeater {
+ id: buttonRepeater
+ property var buttons: [
+ //% "Link"
+ ["\uf0c1", qsTrId("#shareByLink"), serverConn.shareResultsAsUrl],
+ //% "QR-Code"
+ ["\uf029", qsTrId("#shareByQrCode"), function() {
+ stackLayout.currentIndex = 1
+ }
+ ],
+ //% "Poster"
+ ["\uf1c1", qsTrId("#shareByPoster"), serverConn.shareResultsAsPoster],
+ ]
+
+ model: buttons
+
+ delegate: Button {
+ flat: true
+ font.family: fa5solid.name
+ text: "" + modelData[0] + " " + modelData[1] + " "
+ onClicked: buttonRepeater.buttons[index][2](_shareUrl, _compName)
+ }
+ }
+ }
+
+
+ Image {
+ id: qrCodeImage
+
+ property int finalSize: app.landscape() ? app.height * 0.8 : app.width * 0.8
+ property int size: stackLayout.currentIndex === 1 ? finalSize:menuRow.height
+
+ Layout.preferredHeight: size
+ Layout.preferredWidth: size
+
+ sourceSize.width: finalSize
+ sourceSize.height: finalSize
+
+ fillMode: Image.PreserveAspectFit
+
+ source: "image://QZXing/encode/" + _shareUrl + "?border=true&correctionLevel=H"
+
+ RectangularGlow {
+ id: effect
+ anchors.fill: blurRockLogoBackgroundRect
+ glowRadius: 0
+ spread: 0
+ opacity: 0.8
+ color: "black"
+ cornerRadius: blurRockLogoBackgroundRect.radius
+ }
+
+ Rectangle {
+ id: blurRockLogoBackgroundRect
+ anchors.centerIn: parent
+ width: parent.width * 0.25
+ height: width
+ radius: height * 0.2
+ color: "white"
+ Image {
+ anchors.centerIn: parent
+ width: parent.width * 0.8
+ height: width
+ mipmap: true
+ source: "qrc:/icons/blueRockHold.png"
+ }
+ }
+
+ Behavior on size {
+ NumberAnimation {
+ duration: 200
+ }
+ }
+ }
+ }
+ }
+
+ function appear(shareUrl, compName) {
+ _shareUrl = shareUrl
+ _compName = compName
+ shareComponentLoader.sourceComponent = shareComponent
+ control.open()
+ }
+}
diff --git a/resources/qml/Components/SpeedFlowChart.qml b/resources/qml/Components/SpeedFlowChart.qml
index 520852b..26705fd 100644
--- a/resources/qml/Components/SpeedFlowChart.qml
+++ b/resources/qml/Components/SpeedFlowChart.qml
@@ -385,7 +385,7 @@ Item {
width: parent.width
height: roundItem.tileSize
//scale: 0.9
- color: "white"
+ color: Material.dialogColor
border.color: "lightgrey"
border.width: 0
radius: height * 0.2
@@ -438,7 +438,7 @@ Item {
font.bold: matchItm.winnerIndex === index
elide: "ElideRight"
- color: matchItm.winnerIndex === index ? "green":"black"
+ color: matchItm.winnerIndex === index ? Material.color(Material.Green):Material.primaryTextColor
text: matchItm.thisMatchData[index] !== undefined ? matchItm.thisMatchData[index]['firstname'] + " " + matchItm.thisMatchData[index]['lastname'] :"-"
}
diff --git a/resources/qml/Components/SpeedFlowChartLocker.qml b/resources/qml/Components/SpeedFlowChartLocker.qml
index 68d2984..f636caa 100644
--- a/resources/qml/Components/SpeedFlowChartLocker.qml
+++ b/resources/qml/Components/SpeedFlowChartLocker.qml
@@ -1,21 +1,20 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
+import QtQuick.Controls.Material 2.12
import QtQuick.Layouts 1.15
import QtPurchasing 1.12
-Rectangle {
- id: speedFlowChartLockedOverlay
+Page {
+ id: control
anchors.fill: parent
- anchors.margins: -20
-
- color: "white"
Connections {
target: speedFlowChartProduct
function onPurchaseFailed() {
- purchaseBt.text = qsTr("Purchase failed")
+ //% "Purchase failed"
+ purchaseBt.text = qsTrId("#purchaseFailed")
purchaseBt.enabled = false
buttonTextResetTimer.start()
}
@@ -28,62 +27,48 @@ Rectangle {
repeat: false
onTriggered: {
purchaseBt.text = (speedFlowChartProduct.status === Product.Registered
- ? "Buy now for " + speedFlowChartProduct.price
- : qsTr("this item is currently unavailable"))
+ //% "Buy now for"
+ ? qsTrId("#buyNowFor") + " " + speedFlowChartProduct.price
+ //% "This item is currently unavailable"
+ : qsTrId("#itemIsUnavailable"))
purchaseBt.enabled = true
}
}
- ColumnLayout {
+ Column {
id: lockedLayout
anchors {
fill: parent
- topMargin: parent.height * 0.05
+ topMargin: parent.height * 0.02
bottomMargin: parent.height * 0.05
rightMargin: parent.width * 0.1 + 20
leftMargin: parent.width * 0.1 + 20
}
- //spacing: parent.height * 0.05
+ spacing: lockedLayout.height * 0.01
- Image {
- id: name
-
- Layout.alignment: Layout.Center
- Layout.preferredHeight: height
- Layout.preferredWidth: width
-
- width: lockedLayout.height * 0.05
- height: width
-
- mipmap: true
-
- source: "qrc:/icons/lock.png"
- }
-
- Text {
- Layout.fillWidth: true
-
- height: parent.height * 0.05
-
- text: qsTr("This is a premium feature.")
+ Label {
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: lockedLayout.height * 0.03
+ width: lockedLayout.width
+ //% "This is a premium feature."
+ text: qsTrId("#thisIsAPremiumFeature")
+ font.pixelSize: height
font.bold: true
- font.pixelSize: parent.height * 0.05
fontSizeMode: Text.Fit
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
minimumPixelSize: 1
-
- horizontalAlignment: Text.AlignHCenter
-
}
SwipeGallery {
property string platformIcons: Qt.platform.os === "osx" || Qt.platform.os === "iso" ? "ios":"android"
- Layout.fillHeight: true
- Layout.fillWidth: true
+ height: lockedLayout.height * 0.85
+ width: lockedLayout.width
images: [
"qrc:/screenshots/SpeedFlowchartDemo/" + platformIcons + "/1.jpeg",
@@ -94,39 +79,52 @@ Rectangle {
Button {
id: purchaseBt
- Layout.alignment: Layout.Center
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: lockedLayout.height * 0.07
enabled: speedFlowChartProduct.status === Product.Registered
text: speedFlowChartProduct.status === Product.Registered
- ? "Buy now for " + speedFlowChartProduct.price
- : qsTr("This item is currently unavailable")
- icon.name: "buy"
+ ? "\uf218 "+ qsTrId("#buyNowFor") + " " + speedFlowChartProduct.price
+ : qsTrId("#itemIsUnavailable")
+ font.family: fa5solid.name
+ font.pixelSize: height * 0.4
+ font.capitalization: Font.MixedCase
onClicked: speedFlowChartProduct.purchase()
}
RowLayout {
+ id: bottomRow
- Layout.alignment: Layout.Center
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: lockedLayout.height * 0.065
- Button {
- id: restorePurchaseButton
- Layout.alignment: Layout.Center
- visible: speedFlowChartProduct.status === Product.Registered
+ Button {
+ id: restorePurchaseButton
+ Layout.alignment: Layout.Center
+ Layout.preferredHeight: bottomRow.height
+ //visible: speedFlowChartProduct.status === Product.Registered
- flat: true
- text: "restore purchase"
+ flat: true
+ font.pixelSize: height * 0.4
+ font.capitalization: Font.MixedCase
+ //% "Restore purchase"
+ text: qsTrId("#restorePurchase")
- onClicked: inAppProductStore.restorePurchases()
- }
+ onClicked: inAppProductStore.restorePurchases()
+ }
- Button {
- id: contactSupportButton
- Layout.alignment: Layout.Center
+ Button {
+ id: contactSupportButton
+ Layout.alignment: Layout.Center
+ Layout.preferredHeight: bottomRow.height
- flat: true
- text: "contact support"
+ flat: true
+ font.pixelSize: height * 0.4
+ font.capitalization: Font.MixedCase
+ //% "contact support"
+ text: qsTrId("#contact support")
- onClicked: Qt.openUrlExternally("mailto:contact@itsblue.de")
- }
+ onClicked: Qt.openUrlExternally("mailto:contact@itsblue.de")
+ }
}
}
diff --git a/resources/qml/Components/SpeedFlowChartPopup.qml b/resources/qml/Components/SpeedFlowChartPopup.qml
index 5456a8d..3200d82 100644
--- a/resources/qml/Components/SpeedFlowChartPopup.qml
+++ b/resources/qml/Components/SpeedFlowChartPopup.qml
@@ -11,6 +11,9 @@ Rectangle {
//property bool unlocked: QT_DEBUG || appSettings.read("speedBackendPurchase") === "1"
property bool unlocked: appSettings.read("speedBackendPurchase") === "1"
+ Component.onCompleted: {
+ console.warn("unlocked:", appSettings.read("speedBackendPurchase"))
+ }
state: "hidden"
diff --git a/resources/qml/Components/SwipeGallery.qml b/resources/qml/Components/SwipeGallery.qml
index d159c5c..ea07b81 100644
--- a/resources/qml/Components/SwipeGallery.qml
+++ b/resources/qml/Components/SwipeGallery.qml
@@ -14,6 +14,7 @@ Item {
anchors.fill: parent
anchors.margins: 1
anchors.bottomMargin: indicator.height
+ spacing: width * 0.2
Repeater {
model: control.images.length
diff --git a/resources/qml/Pages/QrCodeScanPage.qml b/resources/qml/Pages/QrCodeScanPage.qml
new file mode 100644
index 0000000..7474d1c
--- /dev/null
+++ b/resources/qml/Pages/QrCodeScanPage.qml
@@ -0,0 +1,298 @@
+import QtQuick 2.0
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import QZXing 3.1
+import QtMultimedia 5.12
+import QtQuick.Shapes 1.12
+import QtQuick.Controls.Material 2.12
+
+import "../Components"
+
+Page {
+ id: control
+
+ property string _statusText: ""
+ property string _statusColor: Material.primaryTextColor
+ property bool _freezeScanning: false
+ signal headerComponentChanged()
+
+ //% "Scan QR-Code"
+ title: qsTrId("#scanQrCode")
+
+ onFocusChanged: focus ? open() : close()
+
+ function open() {
+ _setDefaultStatusText()
+ control._freezeScanning = false
+ if(serverConn.isCameraPermissionGranted())
+ cameraLoader.sourceComponent = cameraComponent
+ else
+ cameraLoader.sourceComponent = noPermissionComponent
+ }
+
+ function close() {
+ cameraLoader.sourceComponent = null
+ }
+
+ function _setDefaultStatusText() {
+ //% "Place the Code in the center"
+ _statusText = qsTrId("#placeQrCodeInCenter")
+ _statusColor = Material.primaryTextColor
+ }
+
+ function _handleTag(tag) {
+ if(control._freezeScanning)
+ return
+
+ control._freezeScanning = true
+
+ //% "Plase wait"
+ control._statusText = qsTrId("#pleaseWait") + "..."
+
+ if(app.openWidgetFromUrl(tag))
+ control.close()
+ else {
+ //% "Invalid QR-Code"
+ control._statusText = qsTrId("#invalidQrCode")
+ control._statusColor = Material.color(Material.Red)
+ statusTextResetTimer.start()
+ control._freezeScanning = false
+ }
+ }
+
+ function _requestCameraPermission() {
+ var permissionGranted = serverConn.requestCameraPermission()
+
+ if(permissionGranted)
+ cameraLoader.sourceComponent = cameraComponent
+ }
+
+ contentItem: Loader {
+ id: cameraLoader
+
+ anchors.fill: parent
+
+ //asynchronous: true
+ sourceComponent: null
+ }
+
+ Component {
+ id: cameraComponent
+ Item {
+ anchors.fill: parent
+
+ Camera {
+ id: camera
+ captureMode: Camera.CaptureStillImage
+ imageProcessing.whiteBalanceMode: CameraImageProcessing.WhiteBalanceAuto
+
+ focus {
+ focusMode: Camera.FocusContinuous
+ focusPointMode: Camera.FocusPointCenter
+ }
+ }
+
+ FancyBusyIndicator {
+ anchors.centerIn: parent
+ }
+
+ VideoOutput {
+ id: videoOutput
+ x: 0
+ y: 0
+ width: parent.width
+ height: parent.height
+
+ fillMode: VideoOutput.PreserveAspectCrop
+
+ source: camera
+ filters: [ zxingFilter ]
+ focus : visible // to receive focus and capture key events when visible
+
+ autoOrientation: true
+
+ MouseArea {
+ anchors.fill: parent
+
+ onClicked: {
+ if (camera.lockStatus !== Camera.Unlocked)
+ camera.unlock();
+
+ camera.searchAndLock();
+ }
+ }
+
+ Rectangle {
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: app.landscape() ? focusIndicatorRect.left : parent.right
+ bottom: app.landscape() ? parent.bottom : focusIndicatorRect.top
+ }
+
+ opacity: focusIndicatorRect.opacity
+ color: focusIndicatorRect.border.color
+ }
+
+ Rectangle {
+ id: focusIndicatorRect
+ anchors.centerIn: parent
+
+ width: Math.min(parent.height, parent.width)
+ height: width
+
+ border.width: width * 0.1
+ border.color: "#000000"
+
+ opacity: 0.5
+ color: "transparent"
+ }
+
+ Rectangle {
+ anchors {
+ bottom: focusIndicatorRect.bottom
+ bottomMargin: height * 0.5
+ horizontalCenter: focusIndicatorRect.horizontalCenter
+ }
+
+ width: (focusIndicatorRect.width - focusIndicatorRect.border.width * 2) * 0.8
+ height: focusIndicatorRect.border.width
+
+ radius: height * 0.3
+
+ color: Material.backgroundColor
+
+ Material.elevation: 10
+
+
+ Label {
+ anchors {
+ fill: parent
+ margins: height * 0.1
+ }
+
+ color: control._statusColor
+
+ font.pixelSize: height * 0.5
+ fontSizeMode: Text.Fit
+ minimumPixelSize: height * 0.2
+
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+
+ text: control._statusText
+ }
+ }
+
+ Rectangle {
+ anchors {
+ top: app.landscape() ? parent.top : focusIndicatorRect.bottom
+ left: app.landscape() ? focusIndicatorRect.right : parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+
+ opacity: focusIndicatorRect.opacity
+ color: focusIndicatorRect.border.color
+ }
+ }
+ }
+ }
+
+ Component {
+ id: noPermissionComponent
+ ColumnLayout {
+ //anchors.fill: parent
+ //anchors.margins: app.landscape() ? app.height * 0.1 : app.width * 0.1
+
+ property int columnWidth: control.width * 0.9
+
+ spacing: height * 0.02
+
+ Item {
+ Layout.fillHeight: true
+ }
+
+ Label {
+ id: noPermissionIcon
+
+ Layout.preferredWidth: parent.columnWidth
+ Layout.alignment: Layout.Center
+
+ font.pixelSize: app.landscape() ? parent.height * 0.25:parent.height * 0.15
+ font.family: fa5solid.name
+
+ horizontalAlignment: Text.AlignHCenter
+
+ text: "\uf3ed"
+ }
+ Label {
+ id: noPermissionText
+
+ Layout.preferredWidth: parent.columnWidth
+ Layout.alignment: Layout.Center
+
+ font.bold: true
+ font.pixelSize: noPermissionIcon.height * 0.2
+
+ horizontalAlignment: Text.AlignHCenter
+
+ wrapMode: Text.Wrap
+
+ //% "Camera access required"
+ text: qsTrId("#cameraPermissionDenied")
+ }
+
+ Label {
+ id: noPermissionDetailText
+
+ Layout.preferredWidth: parent.columnWidth
+ Layout.alignment: Layout.Center
+
+ font.pixelSize: noPermissionText.font.pixelSize * 0.7
+
+ horizontalAlignment: Text.AlignHCenter
+
+ wrapMode: Text.Wrap
+
+ //% "blueROCK needs to access your camera in order to scan QR-Codes. It will never record or store any photos or videos."
+ text: qsTrId("#cameraPermissionDeniedDetails")
+ }
+
+ Button {
+ id: grantPermissionButton
+
+ Layout.alignment: Layout.Center
+
+ //% "Allow access"
+ text: qsTrId("#allowAccess")
+
+ onClicked: control._requestCameraPermission()
+ }
+
+
+ Item {
+ Layout.fillHeight: true
+ }
+
+ }
+ }
+
+ QZXingFilter {
+ id: zxingFilter
+
+ decoder {
+ onTagFound: control._handleTag(tag)
+
+ enabledDecoders: QZXing.DecoderFormat_QR_CODE
+ }
+ }
+
+ Timer {
+ id: statusTextResetTimer
+ running: false
+ repeat: false
+ interval: 3000
+ onTriggered: _setDefaultStatusText()
+ }
+}
diff --git a/resources/qml/Pages/StartPage.qml b/resources/qml/Pages/StartPage.qml
index 6289ebb..72dde86 100644
--- a/resources/qml/Pages/StartPage.qml
+++ b/resources/qml/Pages/StartPage.qml
@@ -19,6 +19,7 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.0
+import QtQuick.Controls.Material 2.12
import "../Components"
@@ -37,11 +38,10 @@ Page {
topMargin: root.height * 0.03
}
- height: menuGr.buttonSize * 0.3
- spacing: anchors.topMargin * 0.5
+ height: app.landscape() ? menuGr.buttonSize * 0.2:menuGr.buttonSize * 0.3
}
- Grid {
+ GridLayout {
id: menuGr
anchors.centerIn: parent
@@ -49,17 +49,19 @@ Page {
rows: app.landscape() ? 1:2
columns: app.landscape() ? 2:1
- spacing: !app.landscape() ? parent.height * 0.08:parent.width * 0.1
+ rowSpacing: app.landscape() ? parent.width * 0.1:headerBadge.anchors.topMargin
+ columnSpacing: rowSpacing
- property int buttonSize: app.landscape() ? parent.width * 0.2:parent.height * 0.2
+ property int buttonSize: app.landscape() ? parent.width * 0.2:parent.height * 0.19
FancyButton {
id: davBt
- height: menuGr.buttonSize
- width: height
+ Layout.preferredHeight: menuGr.buttonSize
+ Layout.preferredWidth: menuGr.buttonSize
+ Layout.alignment: Layout.Center
- image: "qrc:/icons/dav.png"
+ image: Material.theme === Material.Dark ? "qrc:/icons/dav-dark.png":"qrc:/icons/dav.png"
onClicked: {
app.openWidget({nation:"GER"})
@@ -70,90 +72,107 @@ Page {
FancyButton {
id: sacBt
- height: menuGr.buttonSize
- width: height
+ Layout.preferredHeight: menuGr.buttonSize
+ Layout.preferredWidth: menuGr.buttonSize
+ Layout.alignment: Layout.Center
- image: "qrc:/icons/sac.png"
+ image: Material.theme === Material.Dark ? "qrc:/icons/sac-dark.png":"qrc:/icons/sac.png"
onClicked: {
app.openWidget({nation:"SUI"})
}
-
- }
-
- }
-
- RowLayout {
- anchors {
- horizontalCenter: parent.horizontalCenter
- bottom: bottomDigitalrockDisclaimerLabel.top
- }
-
- Button {
- id: ifscDisclaimerButton
-
- flat: true
- font.bold: true
- font.pixelSize: aboutBluerockDisclaimerButton.font.pixelSize
-
- text: "Where are the IFSC results?"
-
- onClicked: ifscDisclaimerDialog.open()
- }
-
- Button {
- id: aboutBluerockDisclaimerButton
-
- flat: true
- font.pixelSize: bottomDigitalrockDisclaimerLabel.paintedHeight * (Qt.platform.os === "android" ? 0.8:0.735)
-
- text: "About blueROCK"
-
- onClicked: aboutBluerockDisclaimerDialog.open()
}
}
- Label {
- id: bottomDigitalrockDisclaimerLabel
+ Grid {
+ id: footerMenu
+
anchors {
- horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
- bottomMargin: headerBadge.anchors.topMargin
+ margins: headerBadge.anchors.topMargin
+ horizontalCenter: parent.horizontalCenter
}
- width: parent.width * 0.9
- height: anchors.bottomMargin
+ width: app.landscape() ? childrenRect.width : parent.width * 0.8
+ height: app.landscape() ? headerBadge.height : Math.min(headerBadge.height * 2, width * 0.27)
- fontSizeMode: Label.Fit
- minimumPixelSize: 1
+ columnSpacing: height * 0.1
- horizontalAlignment: Text.AlignHCenter
+ columns: app.landscape() ? 4:2
+ rows: app.landscape() ? 1:2
- text: "Resultservice and rankings provided by digital ROCK ."
+ Repeater {
+ id: buttonRepeater
+ property var buttons: [
+ //% "IFSC results"
+ ["\uf059", qsTrId("#ifscResults"), ifscDisclaimerDialog.open],
+ [
+ "\uf042",
+ Material.theme === Material.Light ?
+ //% "Dark mode"
+ qsTrId("#darkMode"):
+ //% "Light mode"
+ qsTrId("#lightMode"),
+ app.toggleDarkMode
+ ],
+ //% "About blueROCK"
+ ["\uf05a", qsTrId("#aboutBluerock"), aboutBluerockDisclaimerDialog.open],
+ ["\uf029", qsTrId("#scanQrCode"), function(){
+ mainStack.push("qrc:/Pages/QrCodeScanPage.qml")
+ }],
+ ]
- onLinkActivated: {
- Qt.openUrlExternally(link)
+ model: buttons
+
+ delegate: Item {
+
+ width: app.landscape() ? footerMenuButton.implicitWidth : footerMenu.width * 0.5 - (footerMenu.columnSpacing / 2)
+ height: app.landscape() ? footerMenu.height : footerMenu.height * 0.5 - (footerMenu.rowSpacing / 2)
+
+ Button {
+ id: footerMenuButton
+
+ property bool isLeft: index % 2 === 0
+
+ anchors {
+ right: isLeft && !app.landscape() ? parent.right : undefined
+ left: isLeft && !app.landscape() ? undefined : parent.left
+ centerIn: app.landscape() ? parent : undefined
+ }
+
+ height: parent.height
+
+ flat: true
+
+ font.family: fa5solid.name
+ font.pixelSize: height * 0.4
+ font.capitalization: Font.MixedCase
+ //horizontalAlignment: isLeft ? Text.AlignRight : Text.AlignLeft
+
+ text: isLeft && !app.landscape() ? modelData[1] + " " + modelData[0] : modelData[0] + " " + modelData[1]
+
+ onClicked: buttonRepeater.buttons[index][2]()
+ }
+ }
}
}
DisclaimerDialog {
id: ifscDisclaimerDialog
- title: "Where are the IFSC results?"
- content: "Unfortunately, the IFSC has restricted the access to their data and is not willing to share results with blueROCK anymore . " +
- "Because of this, blueROCK is no longer able to access and display IFSC results. " +
- "You can find current IFSC results over here and on their website ."
+ Material.theme: root.Material.theme
+ //% "Where are the IFSC results?"
+ title: qsTrId("#ifscDisclaimerTitle")
+ //% "Unfortunately, the IFSC has restricted the access to their data and is not willing to share results with blueROCK anymore . Because of this, blueROCK is no longer able to access and display IFSC results. You can find current IFSC results over here and on their website ."
+ content: qsTrId("#ifscDisclaimer")
}
DisclaimerDialog {
id: aboutBluerockDisclaimerDialog
- title: "blueROCK v" + APP_VERSION + " By Itsblue Development "
- content: "This app was built using the Qt Framework " +
- "licensed under the GNU lgplV3 license . "+
-
- "This app is open source and licensed under the GNU agplV3 license ," +
- "the source code can be found here ."
-
+ Material.theme: root.Material.theme
+ //% "privacy policy"
+ title: "blueROCK v" + APP_VERSION + " By Itsblue Development , " + qsTrId("#privacyPolicy") + " "
+ //% "This app was built using the Qt Framework licensed under the GNU lgplV3 license . This app is open source and licensed under the GNU agplV3 license , the source code can be found here . Resultservice and rankings provided by digital ROCK ."
+ content: qsTrId("#aboutBluerockDisclaimer")
}
-
}
diff --git a/resources/qml/Pages/WidgetPage.qml b/resources/qml/Pages/WidgetPage.qml
index 5f01e5b..9a5a68a 100644
--- a/resources/qml/Pages/WidgetPage.qml
+++ b/resources/qml/Pages/WidgetPage.qml
@@ -38,7 +38,9 @@ Page {
Result,
Ranking,
- Aggregated // not yet implemented
+ Aggregated, // not yet implemented
+
+ Invalid
}
title: widgetLd.item !== null && widgetLd.item.hasOwnProperty('title') ? widgetLd.item['title']:""
@@ -72,7 +74,6 @@ Page {
// route: (int) round
// type: ('','starters', 'nat_team_ranking', 'sektionenwertung', 'regionalzentren'),
//}
-
var ret = serverConn.getWidgetData(params)
root.status = ret["status"]
@@ -80,7 +81,11 @@ Page {
if(ret["status"] === 200){
root.widgetData = ret["data"]
root.widgetType = checkWidgetType(params, root.widgetData)
- if(widgetLd.load()){
+ if(widgetType === WidgetPage.WidgetType.Invalid) {
+ root.ready = false
+ root.status = 906
+ }
+ else if(widgetLd.load()){
root.ready = true
}
else {
@@ -88,7 +93,13 @@ Page {
root.ready = false
}
}
- else if(ret["status"] === 404 && [WidgetPage.WidgetType.Registration, WidgetPage.WidgetType.Startlist, WidgetPage.WidgetType.Result].includes(root.widgetType) && root.params["route"] !== "") {
+ else if(ret["status"] === 404 &&
+ [
+ WidgetPage.WidgetType.Registration,
+ WidgetPage.WidgetType.Startlist,
+ WidgetPage.WidgetType.Result
+ ].includes(root.widgetType) &&
+ root.params["route"] !== "") {
// if we get a 404 and have startlist, results or registration, the route was not found -> remove round and try again
root.params["route"] = ""
loadData(root.params)
@@ -118,6 +129,10 @@ Page {
}
+ function areParamsValid() {
+
+ }
+
function checkWidgetType(params, widgetData){
var widgetType
@@ -164,10 +179,25 @@ Page {
// aggregated
widgetType = WidgetPage.WidgetType.Aggregated
}
+ else {
+ widgetType = WidgetPage.WidgetType.Invalid
+ }
return widgetType
}
+ function encodeQueryData(data) {
+ const ret = [];
+ for (let d in data)
+ ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
+ return ret.join('&');
+ }
+
+ function shareWidget(compName) {
+ var url = "https://l.bluerock.dev/?" + encodeQueryData(params)
+ sharePu.appear(url, compName)
+ }
+
Loader {
id: widgetLd
@@ -201,9 +231,6 @@ Page {
delete(widgetLd.sourceComponent)
return false
}
-
-
- //
}
function getFile(widgetType) {
@@ -248,11 +275,14 @@ Page {
SelectorPopup {
id: selectorPu
+ Material.theme: root.Material.theme
+
contentItem: ListView {
id: selectorLv
property int delegateHeight: 50
spacing: 10
+ clip: true
implicitHeight: model === 0 ? 0:(delegateHeight + spacing) * model
@@ -267,7 +297,6 @@ Page {
leftMargin: 3
bottom: selectorLv.bottom
}
-
}
delegate: Button {
@@ -287,4 +316,10 @@ Page {
}
}
}
+
+ SharePopup {
+ id: sharePu
+
+ Material.theme: root.Material.theme
+ }
}
diff --git a/resources/qml/Widgets/CalendarWidget.qml b/resources/qml/Widgets/CalendarWidget.qml
index f9785c1..03ae164 100644
--- a/resources/qml/Widgets/CalendarWidget.qml
+++ b/resources/qml/Widgets/CalendarWidget.qml
@@ -19,6 +19,7 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
+import QtQuick.Controls.Material 2.12
import "../Components"
@@ -27,7 +28,8 @@ DataListView {
property bool ready
- property string title: (params.nation === "ICC" ? "IFSC":params.nation === "GER" ? "DAV":"SAC") + " " + qsTr("calendar") + " " + control.year
+ //% "calendar"
+ property string title: (params.nation === "ICC" ? "IFSC":params.nation === "GER" ? "DAV":"SAC") + " " + qsTrId("#calendar") + " " + control.year
property Component headerComponent: RowLayout {
@@ -41,7 +43,9 @@ DataListView {
control.changeYear()
}
- icon.name: "year"
+ text: "\uf133"
+ font.family: fa5solid.name
+
}
ToolButton {
@@ -60,16 +64,19 @@ DataListView {
}
}
- compCats.push( {"text": qsTr("Pinned"), "data": {"sort_rank":0, "cat_id":[-1]}} )
+ //% "Favorites"
+ compCats.push( {"text": qsTrId("#favorites"), "data": {"sort_rank":0, "cat_id":[-1]}} )
compCats.sort(function(a, b) {
return a['data']['sort_rank'] - b['data']['sort_rank'];
});
- filterSelectPu.appear(compCats, qsTr("Select Filters"), "")
+ //% "Select filters"
+ filterSelectPu.appear(compCats, qsTrId("#selectFilters"), "")
}
- icon.name: "filter"
+ text: "\uf0b0"
+ font.family: fa5solid.name
}
ToolButton {
@@ -79,7 +86,8 @@ DataListView {
control.openCup()
}
- icon.name: "cup"
+ text: "\uf091"
+ font.family: fa5solid.name
}
}
@@ -105,10 +113,15 @@ DataListView {
initFilters()
initFavorites()
- if(model){
+ if(model && widgetData["competitions"].length > 0){
control.status = 200
control.ready = true
}
+ else if(widgetData["competitions"].length === 0) {
+ control.status = 404
+ control.ready = false
+ }
+
else {
control.ready = false
control.status = 901
@@ -192,13 +205,18 @@ DataListView {
var infoUrls = getCompInfoUrls(compIndex)
var infosheet = "";
if(infoUrls.length >= 1)
- infosheet += ("" + qsTr('infosheet') + " ")
+ //% "Infosheet"
+ infosheet += ("" + qsTrId("#infosheet") + " ")
if(infoUrls.length === 2)
- infosheet += (", " + qsTr('further infos') + " ")
+ //% "Further infos"
+ infosheet += (", " + qsTrId("#furtherInfos") + " ")
console.log("Infosheet: " + infosheet)
- var eventWebsite = control.widgetData["competitions"][compIndex]["homepage"] !== undefined ? ("" + qsTr('Event Website') + " "):""
+
+ var eventWebsite = control.widgetData["competitions"][compIndex]["homepage"] !== undefined ?
+ //% "Event website"
+ ("" + qsTrId("#eventWebsite") + " "):""
selector.appear(selectOptions, control.widgetData["competitions"][compIndex]['name'], eventWebsite + ((eventWebsite !== "" && infosheet !== "") ? ", ":"") + infosheet )
}
@@ -214,7 +232,8 @@ DataListView {
}
}
- selector.appear(selectOptions, qsTr("select year"))
+ //% "Select year"
+ selector.appear(selectOptions, qsTrId("#selectYear"))
}
function openCup(state, data) {
@@ -226,7 +245,8 @@ DataListView {
if(state === undefined){
// opened for the first time -> select cup
- selectTitle = qsTr("select cup")
+ //% "Select cup"
+ selectTitle = qsTrId("#selectCup")
cups.sort(function(a, b) {
return parseInt(b["SerId"]) - parseInt(a["SerId"]);
@@ -254,7 +274,8 @@ DataListView {
return
}
- selectTitle = cup['name'] + ": " + qsTr("select category")
+ //% "Select category"
+ selectTitle = cup['name'] + ": " + qsTrId("#selectCategory")
// build a list with all cat in the cup out of the cat keys (rkey) given in the cup.cats
for(prop in cup['cats']){
@@ -383,6 +404,10 @@ DataListView {
app.openWidget({cup: data.cup, cat: data.cat})
}
}
+
+ function onLinkActivated(link) {
+ Qt.openUrlExternally(link)
+ }
}
header: Item {
@@ -403,12 +428,13 @@ DataListView {
SelectorPopup {
id: filterSelectPu
+ Material.theme: control.Material.theme
+
contentItem: ListView {
id: selectorLv
property int delegateHeight: 50
spacing: 10
-
clip: true
implicitHeight: model === 0 ? 0:(delegateHeight + spacing) * model
diff --git a/resources/qml/Widgets/ProfileWidget.qml b/resources/qml/Widgets/ProfileWidget.qml
index 5f0caa7..f7a6745 100644
--- a/resources/qml/Widgets/ProfileWidget.qml
+++ b/resources/qml/Widgets/ProfileWidget.qml
@@ -35,6 +35,15 @@ Page {
property var widgetData: currentWidgetData
+ property Component headerComponent: ToolButton {
+ id: shareToolBt
+
+ onClicked: shareWidget(control.title)
+
+ text: "\uf1e0"
+ font.family: fa5solid.name
+ }
+
Component.onCompleted: {
control.ready = true
control.status = 200
@@ -190,7 +199,8 @@ Page {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
- text: qsTr("age") + ": " + widgetData["age"]
+ //% "Age"
+ text: qsTrId("#age") + ": " + widgetData["age"]
}
Label {
@@ -206,7 +216,8 @@ Page {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
- text: qsTr("year of birth") + ": " + widgetData["birthdate"]
+ //% "Year of birth"
+ text: qsTrId("#yearOfBirth") + ": " + widgetData["birthdate"]
}
Label {
@@ -222,7 +233,8 @@ Page {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
- text: qsTr("city") + ": " + widgetData["city"]
+ //% "City"
+ text: qsTrId("#city") + ": " + widgetData["city"]
}
}
@@ -251,7 +263,7 @@ Page {
height: 1
width: parent.width
- color: "black"
+ color: Material.foreground
}
@@ -263,7 +275,11 @@ Page {
flat: true
- text: bestResultsRep.showAllResults ? qsTr("show best results"):qsTr("show all results")
+ text: bestResultsRep.showAllResults ?
+ //% "Show best results"
+ qsTrId("#showBestResults"):
+ //% "Show all results"
+ qsTrId("#showAllResults")
onClicked: {
bestResultsRep.showAllResults = !bestResultsRep.showAllResults
diff --git a/resources/qml/Widgets/RankingWidget.qml b/resources/qml/Widgets/RankingWidget.qml
index 7a3acd8..c63a9b4 100644
--- a/resources/qml/Widgets/RankingWidget.qml
+++ b/resources/qml/Widgets/RankingWidget.qml
@@ -27,12 +27,22 @@ DataListView {
property bool ready
property string title: control.widgetData['comp_name']
- property string subTitle: qsTr("(Ranking)") + " after " + control.widgetData['route_name']
+ //% "(Ranking)"
+ property string subTitle: qsTrId("#rankingHeadline") + " after " + control.widgetData['route_name']
property bool titleIsPageTitle: true
property var widgetData: currentWidgetData
signal closeAll()
+ property Component headerComponent: ToolButton {
+ id: shareToolBt
+
+ onClicked: shareWidget(control.title)
+
+ text: "\uf1e0"
+ font.family: fa5solid.name
+ }
+
Connections {
target: selector
function onSelectionFinished(index, data) {
@@ -80,7 +90,7 @@ DataListView {
}
onPressAndHold: {
- app.openWidget({person:thisData["PerId"]})
+ app.openWidget({person:thisData["PerId"]})
}
ParallelAnimation {
@@ -91,6 +101,8 @@ DataListView {
text: ""
+ highlighted: partDel.thisIndex % 2 == 0
+
onClicked: {
if(state === "closed"){
// close all other delegates
@@ -110,16 +122,6 @@ DataListView {
}
}
- Rectangle {
- anchors.fill: parent
-
- width: partDel.width
-
- color: partDel.thisIndex % 2 == 0 ? "white":"lightgrey"
-
- opacity: 0.2
- }
-
Column {
id: partDelCol
diff --git a/resources/qml/Widgets/RegistrationWidget.qml b/resources/qml/Widgets/RegistrationWidget.qml
index 106d27e..dd4cc75 100644
--- a/resources/qml/Widgets/RegistrationWidget.qml
+++ b/resources/qml/Widgets/RegistrationWidget.qml
@@ -18,6 +18,7 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
+import QtQuick.Layouts 1.12
import "../Components"
@@ -30,17 +31,34 @@ DataListView {
property string subTitle: getSubtitle()
property bool titleIsPageTitle: true
- property Component headerComponent: ToolButton {
- id: moreToolBt
+ property Component headerComponent: RowLayout {
- onClicked: {
- control.changeCat()
+ height: parent.height
+ spacing: 0
+
+ ToolButton {
+ id: shareToolBt
+
+ onClicked: shareWidget(control.title)
+
+ text: "\uf1e0"
+ font.family: fa5solid.name
}
- icon.name: "menu"
+ ToolButton {
+ id: moreToolBt
+
+ onClicked: {
+ control.changeCat()
+ }
+
+ text: "\uf142"
+ font.family: fa5solid.name
+ }
}
property var widgetData: currentWidgetData
+ property var athletes
function getSubtitle() {
var titleString
@@ -55,18 +73,16 @@ DataListView {
}
}
- var addition = "(Registration) "
+ //% "(Registration)"
+ var addition = qsTrId("#registrationHeadline")
- if(titleString !== undefined){
+ if(titleString)
return addition + " " + titleString
- }
- else {
+ else
return ""
- }
-
}
- function getText(index){
+ function getText(athleteData){
var fedName // federation name
@@ -75,21 +91,20 @@ DataListView {
for(var i = 0; i < widgetData["federations"].length; i ++ ){
//console.log("checking " + i + ": cat: " + parseInt(widgetData["categorys"][i]["GrpId"]) + " searched cat: " + root.catId)
- if(widgetData["federations"][i]["fed_id"] === widgetData[ 'athletes' ][index]["reg_fed_id"]){
+ if(widgetData["federations"][i]["fed_id"] === athleteData["reg_fed_id"]){
fedName = widgetData["federations"][i]["shortcut"]
}
}
}
else {
// an international competition -> get nation
-
- fedName = widgetData[ 'athletes' ][index]["nation"]
+ fedName = athleteData["nation"]
}
- return widgetData[ "athletes" ][index]["firstname"] + " " + widgetData[ "athletes" ][index]["lastname"] + " (" + fedName + ")"
+ return athleteData["firstname"] + " " + athleteData["lastname"] + " (" + fedName + ")"
}
- function changeCat(){
+ function changeCat() {
var cats = control.widgetData["categorys"]
cats.sort(function(a, b) {
@@ -106,7 +121,22 @@ DataListView {
}
}
- selector.appear(selectOptions, qsTr("select cat"))
+ selector.appear(selectOptions, qsTrId("#selectCategory"),
+ //% "Show results"
+ "" + qsTrId("#showResults") + " ")
+ }
+
+ function filterAthletes(athletes) {
+ if(!params.cat) {
+ params.cat = control.widgetData["categorys"][0]["GrpId"]
+ }
+
+ var filtered = athletes.filter(function(athlete){
+ var res = String(athlete.cat).toLowerCase() === String(params.cat).toLowerCase()
+ return res
+ })
+
+ return filtered
}
Connections {
@@ -116,14 +146,18 @@ DataListView {
updateData({cat: data.cat}, true)
}
}
+
+ function onLinkActivated(link) {
+ selector.close()
+ var tmpParams = params
+ tmpParams.type = ""
+ app.openWidget(tmpParams)
+ }
}
- status: model === 0 ? 901:200
-
- model: widgetData[ 'athletes' ] === undefined ? 0:widgetData[ 'athletes' ].length
-
Component.onCompleted: {
- if(model > 0){
+ athletes = filterAthletes(widgetData["athletes"])
+ if(athletes.length > 0){
control.ready = true
control.status = 200
}
@@ -137,18 +171,24 @@ DataListView {
updateData({}, false)
}
+ onWidgetDataChanged: {
+ athletes = []
+ athletes = filterAthletes(widgetData["athletes"])
+ }
+
+ model: athletes
+
delegate: ItemDelegate {
id: partDel
property int thisIndex: index
- property var thisData: widgetData[ "athletes" ][index]
-
- width: parent.width
- height: parseInt(thisData.cat) === parseInt(params.cat) ? 50:0
+ property var thisData: modelData
opacity: 0
scale: 0.9
+ width: control.width
+
onThisDataChanged: {
fadeInPa.start()
}
@@ -165,15 +205,7 @@ DataListView {
text: ""
- Rectangle {
- anchors.fill: parent
-
- width: partDel.width
-
- color: partDel.thisIndex % 2 == 0 ? "white":"lightgrey"
-
- opacity: 0.2
- }
+ highlighted: partDel.thisIndex % 2 == 0
Label {
anchors.fill: parent
@@ -186,7 +218,7 @@ DataListView {
elide: "ElideRight"
- text: control.getText(index)
+ text: control.getText(partDel.thisData)
}
}
diff --git a/resources/qml/Widgets/ResultWidget.qml b/resources/qml/Widgets/ResultWidget.qml
index b44c2f4..1ac9ef1 100644
--- a/resources/qml/Widgets/ResultWidget.qml
+++ b/resources/qml/Widgets/ResultWidget.qml
@@ -46,7 +46,8 @@ DataListView {
onClicked: control.changeCat()
- icon.name: "menu"
+ text: "\uf142"
+ font.family: fa5solid.name
}
ToolButton {
@@ -61,7 +62,17 @@ DataListView {
speedFlowChartPopup.toggle()
}
- icon.name: "flowchart"
+ text: "\uf0e8"
+ font.family: fa5solid.name
+ }
+
+ ToolButton {
+ id: shareToolBt
+
+ onClicked: shareWidget(control.title)
+
+ text: "\uf1e0"
+ font.family: fa5solid.name
}
}
@@ -113,25 +124,15 @@ DataListView {
}
function getSubtitle() {
- var titleString
+ var titleString = control.widgetData["route_name"]
- for(var i = 0; i < control.widgetData["categorys"].length; i ++ ){
- //console.log("checking " + i + ": cat: " + parseInt(control.widgetData["categorys"][i]["GrpId"]) + " searched cat: " + params.cat)
- if(parseInt(control.widgetData["categorys"][i]["GrpId"]) === parseInt(params.cat)){
- titleString = control.widgetData["categorys"][i]["name"]
- }
- }
+ //% "(Results)"
+ var addition = qsTrId("#resultsHeadline")
- var addition = qsTr("(Results)")
-
- if(titleString !== undefined){
+ if(titleString)
return addition + " " + titleString
- }
- else {
+ else
return ""
- }
-
-
}
function changeRoute(route) {
@@ -155,7 +156,7 @@ DataListView {
}
}
- selector.appear(selectOptions, qsTr("select cat"))
+ selector.appear(selectOptions, qsTrId("#selectCategory"))
}
Connections {
diff --git a/resources/qml/Widgets/StartlistWidget.qml b/resources/qml/Widgets/StartlistWidget.qml
index 90959d1..4de9d02 100644
--- a/resources/qml/Widgets/StartlistWidget.qml
+++ b/resources/qml/Widgets/StartlistWidget.qml
@@ -19,6 +19,7 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtGraphicalEffects 1.0
+import QtQuick.Layouts 1.12
import "../Components"
@@ -28,32 +29,45 @@ DataListView {
property bool ready
property string title: control.widgetData['comp_name']
- property string subTitle: qsTr("(Startlist)") + " " + control.widgetData['route_name'] //getSubtitle()
+ property string subTitle: getSubtitle()
property bool titleIsPageTitle: true
- property Component headerComponent: ToolButton {
+ property Component headerComponent: RowLayout {
+
+ height: parent.height
+ spacing: 0
+
+ ToolButton {
+ id: shareToolBt
+
+ onClicked: shareWidget(control.title)
+
+ text: "\uf1e0"
+ font.family: fa5solid.name
+ }
+
+ ToolButton {
id: moreToolBt
onClicked: {
control.changeCat()
}
- icon.name: "menu"
+ text: "\uf142"
+ font.family: fa5solid.name
+ }
}
function getSubtitle() {
- var titleString
+ var titleString = control.widgetData["route_name"]
- for(var i = 0; i < control.widgetData["categorys"].length; i ++ ){
- //console.log("checking " + i + ": cat: " + parseInt(control.widgetData["categorys"][i]["GrpId"]) + " searched cat: " + params.cat)
- if(parseInt(control.widgetData["categorys"][i]["GrpId"]) === parseInt(params.cat)){
- titleString = control.widgetData["categorys"][i]["name"]
- }
- }
-
- var addition = qsTr("(Startlist)")
- return addition + " " + titleString
+ //% "(Startlist)"
+ var addition = qsTrId("#startlistHeadline")
+ if(titleString)
+ return addition + " " + titleString
+ else
+ return ""
}
function changeRoute(route) {
@@ -77,7 +91,7 @@ DataListView {
}
}
- selector.appear(selectOptions, qsTr("select cat"))
+ selector.appear(selectOptions, qsTrId("#selectCategory"))
}
Connections {
@@ -116,7 +130,7 @@ DataListView {
property int thisIndex: index
property var thisData: widgetData[ "participants" ][index]
- width: parent.width
+ width: control.width
height: 50
opacity: 0
@@ -138,15 +152,7 @@ DataListView {
text: ""
- Rectangle {
- anchors.fill: parent
-
- width: partDel.width
-
- color: partDel.thisIndex % 2 == 0 ? "white":"lightgrey"
-
- opacity: 0.2
- }
+ highlighted: partDel.thisIndex % 2 == 0
Row {
id: partDelFirstRow
diff --git a/resources/qml/main.qml b/resources/qml/main.qml
index fb5c8df..72c8c6c 100644
--- a/resources/qml/main.qml
+++ b/resources/qml/main.qml
@@ -23,34 +23,102 @@ import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.12
import QtPurchasing 1.12
-import com.itsblue.digitalRockRanking 1.0
+import de.itsblue.blueROCK 1.0
import "./Pages"
import "./Components"
+import "./Widgets"
+
Window {
visible: true
width: 540
height: 960
- title: qsTr("blueROCK")
+ title: "blueROCK"
Page {
id: app
property int errorCode: -1
+ property var colorShade: Material.theme === Material.Light ? Material.Shade300 : Material.Shade700
+ property color nationalAdultsColor: Material.color(Material.Green, colorShade)
+ property color nationalYouthColor: Material.color(Material.LightGreen, colorShade)
+ property color federalColor: Material.color(Material.Grey, colorShade)
+
// comp cats source:
// - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/dav_calendar.php
// - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/sac_calendar.php
property var compCats: {
+ // --- ICC ---
+
+ /*'int' : {
+ 'label' : 'International',
+ 'nation' : 'ICC',
+ 'wettk_reg' : '^[0-9]{2,2}[_^E]{1}[^YJ]{1,1}.*',
+ 'serie_reg' : '^[0-9]{2,2}_(WC|TR){1,1}.*',
+ 'rang_title': 'CUWR continuously updated WORLDRANKING',
+ 'bgcolor' : '#B8C8FF',
+ 'nat_team_ranking' : '',
+ 'cat_id' : [68,86]//[68,69,70,86,259]
+ },*/
+ 'worldcup': {
+ 'label' : 'World Cups',
+ 'nation' : 'ICC',
+ 'bgcolor' : '#B8C8FF',
+ 'sort_rank': 1,
+ 'cat_id' : [69]
+ },
+ 'youth' : {
+ 'label' : 'Youth Events',
+ 'nation' : 'ICC',
+ 'wettk_reg' : '^[0-9]{2,2}(EYC|_J|_Y){1,1}.*',
+ 'serie_reg' : '^[0-9]{2,2}_EYC',
+ 'rang_title': '',
+ 'bgcolor' : '#D8E8FF',
+ 'sort_rank': 2,
+ 'cat_id' : [71,258]
+ },
+ 'cont': {
+ 'label' : 'Continental Events',
+ 'nation' : 'ICC',
+ 'bgcolor' : '#B8C8FF',
+ 'sort_rank': 3,
+ 'cat_id' : [262]
+ },
+ 'masters' : {
+ 'label' : 'Masters and Promo Events',
+ 'nation' : 'ICC',
+ 'wettk_reg' : '^[0-9]{2,2}_[^PWERASL]{1}.*',
+ // 'serie_reg' : '^[0-9]{2,2}_(WC|TR){1,1}.*',
+ // 'rang_title': 'CUWR continuously updated WORLDRANKING',
+ 'bgcolor' : '#F0F0F0',
+ 'sort_rank': 4,
+ 'cat_id' : [70]
+ },
+ 'para' : {
+ 'label' : 'Paraclimbing Events',
+ 'nation' : 'ICC',
+ 'wettk_reg' : '^[0-9]{2,2}_PE.*',
+ 'bgcolor' : '#F0F0F0',
+ 'sort_rank': 5,
+ 'cat_id' : [256,259]
+ },
+ 'games': {
+ 'label': 'Games',
+ 'nation': 'ICC',
+ 'bgcolor' : '#B8C8FF',
+ 'sort_rank': 6,
+ 'cat_id': [68,86]
+ },
// --- GER ---
'ger_meisterschaft' : {
'label' : 'Deutsche Meisterschaft',
'nation' : 'GER',
- 'bgcolor' : '#A8F0A8',
+ 'bgcolor' : app.nationalAdultsColor,//'#A8F0A8',
'sort_rank': 1,
'cat_id' : [57, 59, 60]
},
@@ -61,7 +129,7 @@ Window {
'wettk_reg' : '^[0-9]{2,2}[_J]{1,1}[^WL]+.*',
'serie_reg' : '^[0-9]{2,2}_JC',
// 'rang_title': 'Deutsche Jugend RANGLISTE',
- 'bgcolor' : '#D8FFD8',
+ 'bgcolor' : app.nationalYouthColor,//'#D8FFD8',
'sort_rank': 2,
'cat_id' : [58]
},
@@ -71,7 +139,7 @@ Window {
'wettk_reg' : '^[0-9]{2,2}[_J]{1,1}LM.*',
'serie_reg' : '^[0-9]{2,2}[_J]{1,1}LM.*',
'rang_title': '',
- 'bgcolor' : '#F0F0F0',
+ 'bgcolor' : app.federalColor, //'#F0F0F0',
'sort_rank': 3,
'cat_id' : [61,56]
},
@@ -84,7 +152,7 @@ Window {
'wettk_reg' : '^[0-9]{2,2}_[^R].*',
'serie_reg' : '.*',
'rang_title': 'SWISS RANKING',
- 'bgcolor' : '#A8F0A8',
+ 'bgcolor' : app.nationalAdultsColor, //'#A8F0A8',
'sort_rank': 1,
'cat_id' : [62,63]
},
@@ -94,7 +162,7 @@ Window {
'wettk_reg' : '^[0-9]{2,2}_[^R].*',
'serie_reg' : '.*',
'rang_title': 'SWISS RANKING',
- 'bgcolor' : '#D8FFD8',
+ 'bgcolor' : app.nationalYouthColor, //'#D8FFD8',
'sort_rank': 2,
'cat_id' : [65]
},
@@ -103,16 +171,16 @@ Window {
'nation' : 'SUI',
'wettk_reg' : '^[0-9]{2,2}_RG_.*',
'rang_title': '',
- 'bgcolor' : '#F0F0F0',
+ 'bgcolor' : app.federalColor, //'#F0F0F0',
'sort_rank': 3,
'cat_id' : [64]
},
'sui_ice' : {
'label' : 'Iceclimbing',
'nation' : 'SUI',
- 'wettk_reg' : '^[0-9]{2,2}_RC_.*',
+ 'wparams["valid"]ettk_reg' : '^[0-9]{2,2}_RC_.*',
'rang_title': '',
- 'bgcolor' : '#F0F0F0',
+ 'bgcolor' : app.federalColor, //'#F0F0F0',
'sort_rank': 4,
'cat_id' : [84]
}
@@ -120,7 +188,7 @@ Window {
anchors.fill: parent
- //Material.theme: Material.Dark
+ Material.theme: appSettings.read("darkTheme") === "true" ? Material.Dark:Material.Light
Component.onCompleted: {
//loadingDl.open()
@@ -129,6 +197,18 @@ Window {
//mainStack.push("Pages/AthleteSearchPage.qml")
//openWidget({comp: 11651, cat: 26})
//openWidget({person: 6623})
+ //console.log(JSON.stringify(serverConn.getParamsFromUrl("")))
+ //openWidgetFromUrl("https://l.bluerock.dev/?comp=11501&cat=GER_M")
+ }
+
+ FontLoader {
+ id: fa5solid
+ source: "qrc:/fonts/fa5solid.otf"
+ }
+
+ FontLoader {
+ id: fa5regular
+ source: "qrc:/fonts/fa5regular.otf"
}
Shortcut {
@@ -137,8 +217,12 @@ Window {
onActivated: app.goBack()
}
- ServerConn {
+ BlueRockBackend {
id: serverConn
+
+ onOpenedViaUrl: {
+ app.openWidgetFromUrl(url)
+ }
}
AppSettings {
@@ -225,7 +309,9 @@ Window {
height: parent.height
onClicked: app.goBack()
- icon.name: "back"
+
+ text: "\uf053"
+ font.family: fa5solid.name
}
Column {
@@ -235,20 +321,16 @@ Window {
height: childrenRect.height
width: parent.width - extraComponentLoader.width - toolButton.width - 3 * parent.spacing
- Label {
+ MovingLabel {
id: toolBarTitleLa
+ syncWithLabel: toolBarSubTitleLa
width: parent.width
-
scale: 1
- elide: "ElideRight"
-
font.bold: true
verticalAlignment: Text.AlignVCenter
- color: "black"
-
text: getText()
function getText(){
@@ -277,18 +359,16 @@ Window {
}
}
- Label {
+ MovingLabel {
id: toolBarSubTitleLa
- width: parent.width
- height: text !== "" ? undefined:0
+ visible: text !== ""
- elide: "ElideRight"
+ syncWithLabel: toolBarTitleLa
+ width: parent.width
font.bold: false
- color: "black"
-
text: getText()
function getText(){
@@ -298,7 +378,7 @@ Window {
titleString = mainStack.currentItem.subTitle
}
- return(titleString)
+ return titleString
}
Behavior on text {
@@ -466,7 +546,8 @@ Window {
font.bold: true
color: "white"
- text: "loading..."
+ //% "Loading"
+ text: qsTrId("#loading") + "..."
}
}
@@ -507,6 +588,13 @@ Window {
return app.height < app.width
}
+ function toggleDarkMode() {
+ var dark = app.Material.theme === Material.Light
+
+ app.Material.theme = dark ? Material.Dark : Material.Light
+ appSettings.write("darkTheme", dark)
+ }
+
function largeScreen() {
return Math.min(app.width, app.height) > 750
}
@@ -514,17 +602,36 @@ Window {
function openWidget(params) {
loadingDl.open()
- var calComp = Qt.createComponent("qrc:/Pages/WidgetPage.qml").createObject(null, {"params": params})
- app.errorCode = calComp.status
+ console.log("Opening widget: ", JSON.stringify(params))
- if(calComp.ready) {
- mainStack.push(calComp)
- }
- else {
- delete(calComp)
+ var result = false
+
+ if(Object.keys(params).length) {
+ var calComp = Qt.createComponent("qrc:/Pages/WidgetPage.qml").createObject(null, {"params": params})
+ app.errorCode = calComp.status
+
+ if(calComp.ready) {
+ mainStack.push(calComp)
+ result = true
+ }
+ else {
+ delete(calComp)
+ }
}
loadingDl.close()
+ return result
+ }
+
+ function openWidgetFromUrl(url) {
+ var result = serverConn.getParamsFromUrl(url)
+
+ if(result["valid"]) {
+ openWidget(result["params"])
+ return app.errorCode !== 906
+ }
+
+ return result["valid"]
}
function defaultString(string, defaultString) {
@@ -549,66 +656,36 @@ Window {
// 2 - error
var errorString
- var errorDescription
switch(errorCode) {
case 0:
infoLevel = 2
- errorString = "No connection to server"
- errorDescription = "Please check your internet connection and try again."
+ //% "No connection to server"
+ errorString = qsTrId("#noConnectionError")
break
- case 200:
- infoLevel = 0
- errorString = "Success"
- errorDescription = "The request was successfull"
- break
- case 401:
+ case 404:
infoLevel = 2
- errorString = "Authentication required"
- errorDescription = "The server asked for user credentinals, please chack them and try again"
- break
- case 500:
- infoLevel = 2
- errorString = "Internal server error"
- errorDescription = "The server was unable to process this request, this is probaply the servers fault. Please try again later."
- break
- case 900:
- infoLevel = 2
- errorString = "Internal error"
- errorDescription = "Something went wron internally, this is probaply an inssue in the program code"
+ //% "Not found"
+ errorString = qsTrId("#notFoundError")
break
case 901:
infoLevel = 1
- errorString = "No Data"
- errorDescription = "There is currently no data available. Please try again later."
+ //% "No Data"
+ errorString = qsTrId("#noDataError")
break
- case 902:
- infoLevel = 1
- errorString = "Cached (old) data"
- errorDescription = "Es konnte keine Verbindung zum Server hergestellt werden, aber es sind noch alte Daten gespeichert."
- break
- case 903:
- infoLevel = 1
- errorString = "Ungültiger Aufruf"
- errorDescription = "Die aufgerufene Funktion ist momentan nicht verfügbar, bitte versuche es später erneut."
- break
- case 904:
+ case 906:
infoLevel = 2
- errorString = "Incompatible API"
- errorDescription = "Please make shure that you are using the latest version of this app and try again."
- break
- case 905:
- infoLevel = 1
- errorString = "Loading..."
- errorDescription = "Please wait while we're loading some data"
+ //% "Invalid Request"
+ errorString = qsTrId("#invalidRequestError")
+ errorDescription = "Invalid Request"
break
default:
infoLevel = 2
- errorString = "Unexpected error ("+errorCode+")"
- errorDescription = "Unexpected error while getting data from the server. Please try again later."
+ //% "Unexpected error"
+ errorString = qsTrId("#unexpectedError") + " ("+errorCode+")"
}
- return([infoLevel, errorString, errorDescription])
+ return([infoLevel, errorString])
}
}
diff --git a/resources/qml/qml.qrc b/resources/qml/qml.qrc
index 81bf996..323af13 100644
--- a/resources/qml/qml.qrc
+++ b/resources/qml/qml.qrc
@@ -26,5 +26,10 @@
Components/SpeedFlowChartPopup.qml
Components/BlueRockBadge.qml
Components/DisclaimerDialog.qml
+ Components/ColoredItemDelegate.qml
+ Components/AlignedButton.qml
+ Components/SharePopup.qml
+ Pages/QrCodeScanPage.qml
+ Components/MovingLabel.qml
diff --git a/resources/shared/PosterTemplate.png b/resources/shared/PosterTemplate.png
new file mode 100644
index 0000000..1548bb5
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..152385b
Binary files /dev/null and b/resources/shared/PosterTemplate.xcf differ
diff --git a/resources/shared/fonts/OpenSans-Light.ttf b/resources/shared/fonts/OpenSans-Light.ttf
new file mode 100644
index 0000000..6580d3a
Binary files /dev/null and b/resources/shared/fonts/OpenSans-Light.ttf differ
diff --git a/resources/shared/fonts/fa5regular.otf b/resources/shared/fonts/fa5regular.otf
new file mode 100644
index 0000000..cc0ba27
Binary files /dev/null and b/resources/shared/fonts/fa5regular.otf differ
diff --git a/resources/shared/fonts/fa5solid.otf b/resources/shared/fonts/fa5solid.otf
new file mode 100644
index 0000000..5a83ab9
Binary files /dev/null and b/resources/shared/fonts/fa5solid.otf differ
diff --git a/resources/shared/icons/bluerock/20x20/back.png b/resources/shared/icons/bluerock/20x20/back.png
deleted file mode 100644
index db43e27..0000000
Binary files a/resources/shared/icons/bluerock/20x20/back.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/bookmark.png b/resources/shared/icons/bluerock/20x20/bookmark.png
deleted file mode 100644
index c01e7bf..0000000
Binary files a/resources/shared/icons/bluerock/20x20/bookmark.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/bookmarkFilled.png b/resources/shared/icons/bluerock/20x20/bookmarkFilled.png
deleted file mode 100644
index db854f9..0000000
Binary files a/resources/shared/icons/bluerock/20x20/bookmarkFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/buy.png b/resources/shared/icons/bluerock/20x20/buy.png
deleted file mode 100644
index 9f9c09f..0000000
Binary files a/resources/shared/icons/bluerock/20x20/buy.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/calendar.png b/resources/shared/icons/bluerock/20x20/calendar.png
deleted file mode 100644
index 61cc427..0000000
Binary files a/resources/shared/icons/bluerock/20x20/calendar.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/cup.png b/resources/shared/icons/bluerock/20x20/cup.png
deleted file mode 100644
index d52dbd1..0000000
Binary files a/resources/shared/icons/bluerock/20x20/cup.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/drawer.png b/resources/shared/icons/bluerock/20x20/drawer.png
deleted file mode 100644
index 1e974ef..0000000
Binary files a/resources/shared/icons/bluerock/20x20/drawer.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/filter.png b/resources/shared/icons/bluerock/20x20/filter.png
deleted file mode 100644
index bcc1e8a..0000000
Binary files a/resources/shared/icons/bluerock/20x20/filter.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/flowchart.png b/resources/shared/icons/bluerock/20x20/flowchart.png
deleted file mode 100644
index 6469b0a..0000000
Binary files a/resources/shared/icons/bluerock/20x20/flowchart.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/menu.png b/resources/shared/icons/bluerock/20x20/menu.png
deleted file mode 100644
index a10473d..0000000
Binary files a/resources/shared/icons/bluerock/20x20/menu.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/pin.png b/resources/shared/icons/bluerock/20x20/pin.png
deleted file mode 100644
index 9908baf..0000000
Binary files a/resources/shared/icons/bluerock/20x20/pin.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/pinFilled.png b/resources/shared/icons/bluerock/20x20/pinFilled.png
deleted file mode 100644
index fe79afb..0000000
Binary files a/resources/shared/icons/bluerock/20x20/pinFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/star.png b/resources/shared/icons/bluerock/20x20/star.png
deleted file mode 100644
index 55d76f3..0000000
Binary files a/resources/shared/icons/bluerock/20x20/star.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/starFilled.png b/resources/shared/icons/bluerock/20x20/starFilled.png
deleted file mode 100644
index 78aec7c..0000000
Binary files a/resources/shared/icons/bluerock/20x20/starFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20/year.png b/resources/shared/icons/bluerock/20x20/year.png
deleted file mode 100644
index 26f4edc..0000000
Binary files a/resources/shared/icons/bluerock/20x20/year.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/back.png b/resources/shared/icons/bluerock/20x20@2/back.png
deleted file mode 100644
index c55ab31..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/back.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/bookmark.png b/resources/shared/icons/bluerock/20x20@2/bookmark.png
deleted file mode 100644
index 47d18cb..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/bookmark.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/bookmarkFilled.png b/resources/shared/icons/bluerock/20x20@2/bookmarkFilled.png
deleted file mode 100644
index 1dcf9d4..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/bookmarkFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/buy.png b/resources/shared/icons/bluerock/20x20@2/buy.png
deleted file mode 100644
index 2ae229d..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/buy.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/calendar.png b/resources/shared/icons/bluerock/20x20@2/calendar.png
deleted file mode 100644
index 53fe9b1..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/calendar.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/cup.png b/resources/shared/icons/bluerock/20x20@2/cup.png
deleted file mode 100644
index b66aa2f..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/cup.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/drawer.png b/resources/shared/icons/bluerock/20x20@2/drawer.png
deleted file mode 100644
index eba3b6c..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/drawer.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/filter.png b/resources/shared/icons/bluerock/20x20@2/filter.png
deleted file mode 100644
index 1f88edc..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/filter.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/flowchart.png b/resources/shared/icons/bluerock/20x20@2/flowchart.png
deleted file mode 100644
index c87cc2e..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/flowchart.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/menu.png b/resources/shared/icons/bluerock/20x20@2/menu.png
deleted file mode 100644
index 649c2a0..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/menu.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/pin.png b/resources/shared/icons/bluerock/20x20@2/pin.png
deleted file mode 100644
index 0af60ed..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/pin.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/pinFilled.png b/resources/shared/icons/bluerock/20x20@2/pinFilled.png
deleted file mode 100644
index 8c3b904..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/pinFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/star.png b/resources/shared/icons/bluerock/20x20@2/star.png
deleted file mode 100644
index 4104d59..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/star.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/starFilled.png b/resources/shared/icons/bluerock/20x20@2/starFilled.png
deleted file mode 100644
index 4ff5390..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/starFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@2/year.png b/resources/shared/icons/bluerock/20x20@2/year.png
deleted file mode 100644
index 980d2ee..0000000
Binary files a/resources/shared/icons/bluerock/20x20@2/year.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/back.png b/resources/shared/icons/bluerock/20x20@3/back.png
deleted file mode 100644
index b228eb8..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/back.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/bookmark.png b/resources/shared/icons/bluerock/20x20@3/bookmark.png
deleted file mode 100644
index ca812b1..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/bookmark.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/bookmarkFilled.png b/resources/shared/icons/bluerock/20x20@3/bookmarkFilled.png
deleted file mode 100644
index 0dfcd81..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/bookmarkFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/buy.png b/resources/shared/icons/bluerock/20x20@3/buy.png
deleted file mode 100644
index a34fe40..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/buy.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/calendar.png b/resources/shared/icons/bluerock/20x20@3/calendar.png
deleted file mode 100644
index 434503c..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/calendar.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/cup.png b/resources/shared/icons/bluerock/20x20@3/cup.png
deleted file mode 100644
index 9ad32ba..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/cup.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/drawer.png b/resources/shared/icons/bluerock/20x20@3/drawer.png
deleted file mode 100644
index 3584ed6..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/drawer.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/filter.png b/resources/shared/icons/bluerock/20x20@3/filter.png
deleted file mode 100644
index b457f02..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/filter.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/flowchart.png b/resources/shared/icons/bluerock/20x20@3/flowchart.png
deleted file mode 100644
index 98bd45f..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/flowchart.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/menu.png b/resources/shared/icons/bluerock/20x20@3/menu.png
deleted file mode 100644
index 9554b69..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/menu.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/pin.png b/resources/shared/icons/bluerock/20x20@3/pin.png
deleted file mode 100644
index 306add5..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/pin.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/pinFilled.png b/resources/shared/icons/bluerock/20x20@3/pinFilled.png
deleted file mode 100644
index c7eac9c..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/pinFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/star.png b/resources/shared/icons/bluerock/20x20@3/star.png
deleted file mode 100644
index 0b03be1..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/star.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/starFilled.png b/resources/shared/icons/bluerock/20x20@3/starFilled.png
deleted file mode 100644
index 3984352..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/starFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@3/year.png b/resources/shared/icons/bluerock/20x20@3/year.png
deleted file mode 100644
index 5c2eb8f..0000000
Binary files a/resources/shared/icons/bluerock/20x20@3/year.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/back.png b/resources/shared/icons/bluerock/20x20@4/back.png
deleted file mode 100644
index dd157e7..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/back.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/bookmark.png b/resources/shared/icons/bluerock/20x20@4/bookmark.png
deleted file mode 100644
index c82e389..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/bookmark.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/bookmarkFilled.png b/resources/shared/icons/bluerock/20x20@4/bookmarkFilled.png
deleted file mode 100644
index a021439..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/bookmarkFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/buy.png b/resources/shared/icons/bluerock/20x20@4/buy.png
deleted file mode 100644
index ef6c793..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/buy.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/calendar.png b/resources/shared/icons/bluerock/20x20@4/calendar.png
deleted file mode 100644
index d799f8c..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/calendar.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/cup.png b/resources/shared/icons/bluerock/20x20@4/cup.png
deleted file mode 100644
index 4baaf9a..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/cup.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/drawer.png b/resources/shared/icons/bluerock/20x20@4/drawer.png
deleted file mode 100644
index 60d93af..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/drawer.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/filter.png b/resources/shared/icons/bluerock/20x20@4/filter.png
deleted file mode 100644
index 891fcae..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/filter.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/flowchart.png b/resources/shared/icons/bluerock/20x20@4/flowchart.png
deleted file mode 100644
index dc859de..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/flowchart.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/menu.png b/resources/shared/icons/bluerock/20x20@4/menu.png
deleted file mode 100644
index 187c171..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/menu.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/pin.png b/resources/shared/icons/bluerock/20x20@4/pin.png
deleted file mode 100644
index e193f91..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/pin.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/pinFilled.png b/resources/shared/icons/bluerock/20x20@4/pinFilled.png
deleted file mode 100644
index f92d9a9..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/pinFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/star.png b/resources/shared/icons/bluerock/20x20@4/star.png
deleted file mode 100644
index cd38f03..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/star.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/starFilled.png b/resources/shared/icons/bluerock/20x20@4/starFilled.png
deleted file mode 100644
index 7895700..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/starFilled.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/20x20@4/year.png b/resources/shared/icons/bluerock/20x20@4/year.png
deleted file mode 100644
index bc9ab79..0000000
Binary files a/resources/shared/icons/bluerock/20x20@4/year.png and /dev/null differ
diff --git a/resources/shared/icons/bluerock/index.theme b/resources/shared/icons/bluerock/index.theme
deleted file mode 100644
index 2457fde..0000000
--- a/resources/shared/icons/bluerock/index.theme
+++ /dev/null
@@ -1,24 +0,0 @@
-[Icon Theme]
-Name=blueROCK
-Comment=blueROCK Icon Theme
-
-Directories=20x20,20x20@2,20x20@3,20x20@4
-
-[20x20]
-Size=20
-Type=Fixed
-
-[20x20@2]
-Size=20
-Scale=2
-Type=Fixed
-
-[20x20@3]
-Size=20
-Scale=3
-Type=Fixed
-
-[20x20@4]
-Size=20
-Scale=4
-Type=Fixed
diff --git a/resources/shared/icons/dav-dark.png b/resources/shared/icons/dav-dark.png
new file mode 100644
index 0000000..ff29824
Binary files /dev/null and b/resources/shared/icons/dav-dark.png differ
diff --git a/resources/shared/icons/dig_rock.klein.jpg b/resources/shared/icons/dig_rock.klein.jpg
deleted file mode 100644
index 7170811..0000000
Binary files a/resources/shared/icons/dig_rock.klein.jpg and /dev/null differ
diff --git a/resources/shared/icons/dig_rock.klein.png b/resources/shared/icons/dig_rock.klein.png
deleted file mode 100644
index 0eb0963..0000000
Binary files a/resources/shared/icons/dig_rock.klein.png and /dev/null differ
diff --git a/resources/shared/icons/ifsc.png b/resources/shared/icons/ifsc.png
deleted file mode 100644
index 993f23a..0000000
Binary files a/resources/shared/icons/ifsc.png and /dev/null differ
diff --git a/resources/shared/icons/sac-dark.png b/resources/shared/icons/sac-dark.png
new file mode 100644
index 0000000..eae3e00
Binary files /dev/null and b/resources/shared/icons/sac-dark.png differ
diff --git a/resources/shared/shared.qrc b/resources/shared/shared.qrc
index 5c58276..5475fd3 100644
--- a/resources/shared/shared.qrc
+++ b/resources/shared/shared.qrc
@@ -1,7 +1,6 @@
icons/dav.png
- icons/ifsc.png
icons/sac.png
icons/back.png
icons/backDark.png
@@ -9,70 +8,7 @@
icons/cup.png
Banner.png
icons/more_black.png
- icons/bluerock/20x20/back.png
- icons/bluerock/20x20/cup.png
- icons/bluerock/20x20/drawer.png
- icons/bluerock/20x20/menu.png
- icons/bluerock/20x20@2/back.png
- icons/bluerock/20x20@2/cup.png
- icons/bluerock/20x20@2/drawer.png
- icons/bluerock/20x20@2/menu.png
- icons/bluerock/20x20@3/back.png
- icons/bluerock/20x20@3/cup.png
- icons/bluerock/20x20@3/drawer.png
- icons/bluerock/20x20@3/menu.png
- icons/bluerock/20x20@4/back.png
- icons/bluerock/20x20@4/cup.png
- icons/bluerock/20x20@4/drawer.png
- icons/bluerock/20x20@4/menu.png
- icons/bluerock/index.theme
- icons/dig_rock.klein.jpg
- icons/dig_rock.klein.png
- icons/bluerock/20x20/calendar.png
- icons/bluerock/20x20@2/calendar.png
- icons/bluerock/20x20@3/calendar.png
- icons/bluerock/20x20@4/calendar.png
- icons/bluerock/20x20/filter.png
- icons/bluerock/20x20/year.png
- icons/bluerock/20x20@2/filter.png
- icons/bluerock/20x20@2/year.png
- icons/bluerock/20x20@3/filter.png
- icons/bluerock/20x20@3/year.png
- icons/bluerock/20x20@4/filter.png
- icons/bluerock/20x20@4/year.png
- icons/bluerock/20x20/flowchart.png
- icons/bluerock/20x20@2/flowchart.png
- icons/bluerock/20x20@3/flowchart.png
- icons/bluerock/20x20@4/flowchart.png
- icons/bluerock/20x20/bookmark.png
- icons/bluerock/20x20/bookmarkFilled.png
- icons/bluerock/20x20@2/bookmark.png
- icons/bluerock/20x20@2/bookmarkFilled.png
- icons/bluerock/20x20@3/bookmark.png
- icons/bluerock/20x20@3/bookmarkFilled.png
- icons/bluerock/20x20@4/bookmark.png
- icons/bluerock/20x20@4/bookmarkFilled.png
- icons/bluerock/20x20/buy.png
- icons/bluerock/20x20@2/buy.png
- icons/bluerock/20x20@3/buy.png
- icons/bluerock/20x20@4/buy.png
icons/lock.png
- icons/bluerock/20x20/star.png
- icons/bluerock/20x20/starFilled.png
- icons/bluerock/20x20@2/star.png
- icons/bluerock/20x20@2/starFilled.png
- icons/bluerock/20x20@3/star.png
- icons/bluerock/20x20@3/starFilled.png
- icons/bluerock/20x20@4/star.png
- icons/bluerock/20x20@4/starFilled.png
- icons/bluerock/20x20/pin.png
- icons/bluerock/20x20/pinFilled.png
- icons/bluerock/20x20@2/pin.png
- icons/bluerock/20x20@2/pinFilled.png
- icons/bluerock/20x20@3/pin.png
- icons/bluerock/20x20@3/pinFilled.png
- icons/bluerock/20x20@4/pin.png
- icons/bluerock/20x20@4/pinFilled.png
icons/blueRockHold.png
screenshots/SpeedFlowchartDemo/android/1.jpeg
screenshots/SpeedFlowchartDemo/android/2.jpeg
@@ -80,5 +16,11 @@
screenshots/SpeedFlowchartDemo/ios/1.jpeg
screenshots/SpeedFlowchartDemo/ios/2.jpeg
screenshots/SpeedFlowchartDemo/ios/3.jpeg
+ icons/dav-dark.png
+ icons/sac-dark.png
+ fonts/fa5regular.otf
+ fonts/fa5solid.otf
+ PosterTemplate.png
+ fonts/OpenSans-Light.ttf
diff --git a/resources/translations/de.qm b/resources/translations/de.qm
new file mode 100644
index 0000000..919c85c
Binary files /dev/null and b/resources/translations/de.qm differ
diff --git a/resources/translations/de.ts b/resources/translations/de.ts
new file mode 100644
index 0000000..5b1713c
--- /dev/null
+++ b/resources/translations/de.ts
@@ -0,0 +1,276 @@
+
+
+
+
+
+
+
+
+ Scan QR-Code
+ QR-Code scannen
+
+
+
+ Place the Code in the center
+ Positioniere den Code in der Mitte
+
+
+
+ Camera access required
+ Camera access denied
+ Kamerazugriff benötigt
+
+
+
+ blueROCK needs to access your camera in order to scan QR-Codes. It will never record or store any photos or videos.
+ This app needs to access your camera in order to scan QR-Codes. It will never record or store any photos or videos.
+ blueROCK benötig Zugriff auf die Kamera um QR-Codes zu scannen. Es werden keine Fotos oder Videos aufgezeichnet oder gespeichert.
+
+
+
+ Allow access
+ Zugriff zulassen
+
+
+
+ Plase wait
+ Bitte warten
+
+
+
+ Invalid QR-Code
+ Ungültiger QR-Code
+
+
+
+ Share these results
+ Teile diese Ergebnisse
+
+
+
+ Link
+ Link
+
+
+
+ QR-Code
+ QR-Code
+
+
+
+ Poster
+ Poster
+
+
+
+ Purchase failed
+ Kauf fehlgeschlagen
+
+
+
+
+ Buy now for
+ Jetzt kaufen für
+
+
+ This item is currently unavailable
+ Diese Produkt ist nicht verfügbar
+
+
+
+ This is a premium feature.
+ Das ist eine premium Funktion.
+
+
+
+
+ This item is currently unavailable
+ Dieses Produkt ist nicht verfügbar
+
+
+
+ Restore purchase
+ Kauf wiederherstellen
+
+
+
+ contact support
+ Support kontaktieren
+
+
+
+ IFSC results
+ IFSC Ergebnisse
+
+
+
+ Dark mode
+ Dunkler Modus
+
+
+
+ Light mode
+ Heller Modus
+
+
+
+ About blueROCK
+ Über blueROCK
+
+
+
+ Where are the IFSC results?
+ Wo sind die IFSC Ergebnisse?
+
+
+
+ Unfortunately, the IFSC has restricted the access to their data and <b>is not willing to share results with blueROCK anymore</b>. Because of this, blueROCK is no longer able to access and display IFSC results.<br><br>You can find current IFSC results <a href="https://ifsc.results.info">over here</a> and on <a href="https://ifsc-climbing.org">their website</a>.
+ Leider hat die IFSC den Zugang zu ihren Ergebnissen eingeschränkt <b>und ist nicht mehr bereit, Ergebnisse mit blueROCK zu teilen</b>. Daher ist blueROCK nicht länger in der Lage auf IFSC Ergebnisse zuzugriefen und diese anzuzeigen.<br><br>Aktuelle IFSC Ergebnisse finden sich <a href="https://ifsc.results.info">hier</a> und auf der <a href="https://ifsc-climbing.org">IFSC Webseite</a>.
+
+
+
+ privacy policy
+ Datenschutzerklärung
+
+
+
+ This app was built using the <a href='https://qt.io'>Qt Framework</a> licensed under the <a href='https://www.gnu.org/licenses/lgpl-3.0.en.html'>GNU lgplV3 license</a>.<br><br>This app is open source and licensed under the <a href='https://www.gnu.org/licenses/agpl-3.0.en.html'>GNU agplV3 license</a>, the source code can be found <a href='https://itsblue.dev/blueROCK/app'>here</a>.<br><br>Resultservice and rankings provided by <a href='http://www.digitalROCK.de'>digital ROCK</a>.
+ This app was built using the <a href='https://qt.io'>Qt Framework</a> licensed under the <a href='https://www.gnu.org/licenses/lgpl-3.0.en.html'>GNU lgplV3 license</a>.<br><br>This app is open source and licensed under the <a href='https://www.gnu.org/licenses/agpl-3.0.en.html'>GNU agplV3 license</a>, the source code can be found <a href='https://itsblue.dev/dorian/blueROCK/'>here</a>.<br><br>Resultservice and rankings provided by <a href='http://www.digitalROCK.de'>digital ROCK</a>.
+ Diese App wurde unter Verwendung des <a href='https://qt.io'>Qt Frameworks</a> unter der <a href='https://www.gnu.org/licenses/lgpl-3.0.en.html'>GNU lgplV3 Lizenz</a> erstellt.<br><br>Diese App ist Open-source und lizensiert unter der <a href='https://www.gnu.org/licenses/agpl-3.0.en.html'>GNU agplV3 Lizenz</a>. Der Sourcecode findet sich <a href='https://itsblue.dev/blueROCK/app'>hier</a>.<br><br>Die Ergebnisse und Ranglisten werden von <a href='http://www.digitalROCK.de'>digital ROCK</a> zur Verfügung gestellt.
+
+
+
+ calendar
+ Kalender
+
+
+
+ Favorites
+ Favoriten
+
+
+
+ Select filters
+ Filter auswählen
+
+
+
+ Infosheet
+ Ausschreibung
+
+
+
+ Further infos
+ Weitere Informationen
+
+
+
+ Event website
+ Veranstaltungswebseite
+
+
+
+ Select year
+ Jahr auswählen
+
+
+
+ Select cup
+ Rangliste auswählen
+
+
+
+
+
+
+ Select category
+ Kategorie auswählen
+
+
+
+ Age
+ Alter
+
+
+
+ Year of birth
+ Geburtsjahr
+
+
+
+ City
+ Stadt
+
+
+
+ Show best results
+ Zeige die besten Ergebnisse
+
+
+
+ Show all results
+ Zeige alle Ergebnisse
+
+
+
+ (Ranking)
+ (Rangliste)
+
+
+
+ (Registration)
+ (Registrierung)
+
+
+
+ Show results
+ Ergebnisse anzeigen
+
+
+
+ (Results)
+ (Ergebnisse)
+
+
+
+ (Startlist)
+ (Startliste)
+
+
+
+ Loading
+ Laden
+
+
+
+ No connection to server
+ Keine Verbindung zum Server
+
+
+
+ Not found
+ Nicht gefunden
+
+
+
+ No Data
+ Keine Daten
+
+
+
+ Invalid Request
+ Ungültige Anfrage
+
+
+
+ Unexpected error
+ Unerwarteter Fehler
+
+
+
+ Check out the results of %1 over here:
+ Check out the results of %1 over here:
+ Verfolge die Ergebnisse von "%1" hier:
+
+
+
diff --git a/resources/translations/en.qm b/resources/translations/en.qm
new file mode 100644
index 0000000..6633c42
Binary files /dev/null and b/resources/translations/en.qm differ
diff --git a/resources/translations/en.ts b/resources/translations/en.ts
new file mode 100644
index 0000000..ac3c954
--- /dev/null
+++ b/resources/translations/en.ts
@@ -0,0 +1,272 @@
+
+
+
+
+
+
+
+
+ Scan QR-Code
+
+
+
+
+ Place the Code in the center
+
+
+
+
+ Camera access required
+ Camera access denied
+
+
+
+
+ blueROCK needs to access your camera in order to scan QR-Codes. It will never record or store any photos or videos.
+ This app needs to access your camera in order to scan QR-Codes. It will never record or store any photos or videos.
+
+
+
+
+ Allow access
+
+
+
+
+ Plase wait
+
+
+
+
+ Invalid QR-Code
+
+
+
+
+ Share these results
+
+
+
+
+ Link
+
+
+
+
+ QR-Code
+
+
+
+
+ Poster
+
+
+
+
+ Purchase failed
+
+
+
+
+
+ Buy now for
+
+
+
+
+ This is a premium feature.
+
+
+
+
+
+ This item is currently unavailable
+
+
+
+
+ Restore purchase
+
+
+
+
+ contact support
+
+
+
+
+ IFSC results
+
+
+
+
+ Dark mode
+
+
+
+
+ Light mode
+
+
+
+
+ About blueROCK
+
+
+
+
+ Where are the IFSC results?
+
+
+
+
+ Unfortunately, the IFSC has restricted the access to their data and <b>is not willing to share results with blueROCK anymore</b>. Because of this, blueROCK is no longer able to access and display IFSC results.<br><br>You can find current IFSC results <a href="https://ifsc.results.info">over here</a> and on <a href="https://ifsc-climbing.org">their website</a>.
+
+
+
+
+ privacy policy
+
+
+
+
+ This app was built using the <a href='https://qt.io'>Qt Framework</a> licensed under the <a href='https://www.gnu.org/licenses/lgpl-3.0.en.html'>GNU lgplV3 license</a>.<br><br>This app is open source and licensed under the <a href='https://www.gnu.org/licenses/agpl-3.0.en.html'>GNU agplV3 license</a>, the source code can be found <a href='https://itsblue.dev/blueROCK/app'>here</a>.<br><br>Resultservice and rankings provided by <a href='http://www.digitalROCK.de'>digital ROCK</a>.
+ This app was built using the <a href='https://qt.io'>Qt Framework</a> licensed under the <a href='https://www.gnu.org/licenses/lgpl-3.0.en.html'>GNU lgplV3 license</a>.<br><br>This app is open source and licensed under the <a href='https://www.gnu.org/licenses/agpl-3.0.en.html'>GNU agplV3 license</a>, the source code can be found <a href='https://itsblue.dev/dorian/blueROCK/'>here</a>.<br><br>Resultservice and rankings provided by <a href='http://www.digitalROCK.de'>digital ROCK</a>.
+
+
+
+
+ calendar
+
+
+
+
+ Favorites
+
+
+
+
+ Select filters
+
+
+
+
+ Infosheet
+
+
+
+
+ Further infos
+
+
+
+
+ Event website
+
+
+
+
+ Select year
+
+
+
+
+ Select cup
+
+
+
+
+
+
+
+ Select category
+
+
+
+
+ Age
+
+
+
+
+ Year of birth
+
+
+
+
+ City
+
+
+
+
+ Show best results
+
+
+
+
+ Show all results
+
+
+
+
+ (Ranking)
+
+
+
+
+ (Registration)
+
+
+
+
+ Show results
+
+
+
+
+ (Results)
+
+
+
+
+ (Startlist)
+
+
+
+
+ Loading
+
+
+
+
+ No connection to server
+
+
+
+
+ Not found
+
+
+
+
+ No Data
+
+
+
+
+ Invalid Request
+
+
+
+
+ Unexpected error
+
+
+
+
+ Check out the results of %1 over here:
+ Check out the results of %1 over here:
+
+
+
+
diff --git a/resources/translations/translations.qrc b/resources/translations/translations.qrc
new file mode 100644
index 0000000..a028120
--- /dev/null
+++ b/resources/translations/translations.qrc
@@ -0,0 +1,6 @@
+
+
+ de.qm
+ en.qm
+
+
diff --git a/sources/appsettings.cpp b/sources/appsettings.cpp
index 9d98c94..b0f377c 100644
--- a/sources/appsettings.cpp
+++ b/sources/appsettings.cpp
@@ -15,8 +15,6 @@ AppSettings::AppSettings(QObject* parent)
// set the values to their defaults if they haven't been created yet
- // create or open the settings.ini file
- this->themeSettingsManager = new QSettings(":/themes/" + this->read("theme") + ".ini", QSettings::IniFormat);
}
QString AppSettings::read(const QString &key)
@@ -26,7 +24,7 @@ QString AppSettings::read(const QString &key)
// open the value-group
this->settingsManager->beginGroup("AppSettings");
// read the value
- QString value = this->settingsManager->value(key , false).toString();
+ QString value = this->settingsManager->value(key, false).toString();
// close the value-group
this->settingsManager->endGroup();
// return the value
@@ -51,7 +49,7 @@ void AppSettings::setDefault(const QString &key, const QVariant &defaultValue)
// read the current value
QString value = this->read(key);
- if(value == "false"){
+ if(value == "false") {
// if it is nor defined yet, the read function will return "false" (as a string)
// -> if that is the case -> create the key with the default value
this->write(key, defaultValue);
diff --git a/sources/bluerockbackend.cpp b/sources/bluerockbackend.cpp
new file mode 100644
index 0000000..8abc6c9
--- /dev/null
+++ b/sources/bluerockbackend.cpp
@@ -0,0 +1,323 @@
+/*
+ blueROCK - for digital rock
+ Copyright (C) 2019 Dorian Zedler
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "headers/bluerockbackend.h"
+
+BlueRockBackend::BlueRockBackend(QObject *parent) : QObject(parent), _pendingIntentsChecked(false)
+{
+ this->_shareUtils = new ShareUtils(this);
+ connect(this->_shareUtils, &ShareUtils::otherUrlReceived, this, &BlueRockBackend::openedViaUrl);
+
+#if defined(Q_OS_ANDROID)
+ connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(onApplicationStateChanged(Qt::ApplicationState)));
+#elif defined Q_OS_IOS
+ this->_iosPermissionUtils = new IosPermissionUtils();
+#endif
+}
+
+QVariant BlueRockBackend::getWidgetData(QVariantMap params) {
+ QString requestUrl = "https://www.digitalrock.de/egroupware/ranking/json.php?";
+ QStringList nations = {"ICC", "GER", "SUI"};
+ if(params["nation"].toString() == "ICC") {
+ params["nation"] = "";
+ }
+ else if(params["nation"].toString() == "") {
+ params.remove("nation");
+ }
+ else if(!nations.contains(params["nation"].toString())) {
+ // a non-empty nation which ist not one of the above is invalid
+ return QVariantMap({{"status", 404}});
+ }
+
+ for(QVariantMap::const_iterator iter = params.begin(); iter != params.end(); ++iter) {
+ requestUrl += iter.key() + "=" + iter.value().toString() + "&";
+ }
+
+ requestUrl = requestUrl.left(requestUrl.length() - 1); // remove last '&'
+
+ qWarning() << requestUrl;
+
+ QVariantMap ret = this->_senddata(QUrl(requestUrl));
+
+ if(ret["status"] != 200) {
+ // request was a failure
+ return QVariantMap({{"status", ret["status"]}, {"data", ""}});
+ }
+
+ QJsonDocument jsonReply = QJsonDocument::fromJson(ret["text"].toString().toUtf8());
+
+ QVariantMap data = {{"status", 200}, {"data", jsonReply.toVariant()}};
+
+ return data;
+}
+
+
+QVariantMap BlueRockBackend::getParamsFromUrl(QString stringUrl) {
+ stringUrl = stringUrl.replace("#!", "?");
+ QUrl url(stringUrl);
+
+ if(!url.isValid() || url.isEmpty() || url.host().isEmpty())
+ return {{"valid", false},{"params", QVariantMap()}} ;
+
+ QStringList domainFragments = url.host().split(".");
+ QString tld = domainFragments.takeLast();
+ QString domainName = domainFragments.takeLast();
+ QString baseDomain = domainName + "." + tld;
+
+ if(!this->_validBaseDomains.contains(baseDomain))
+ return {{"valid", false},{"params", QVariantMap()}};
+
+ QUrlQuery query(url.query());
+
+ QVariantMap params;
+
+ for(QPair pair : query.queryItems()) {
+ if(params.contains(pair.first))
+ params[pair.first] = pair.second;
+ else
+ params.insert(pair.first, pair.second);
+ }
+
+ return {{"valid", true},{"params",params}};
+}
+
+void BlueRockBackend::shareResultsAsUrl(QString url, QString compName) {
+ //% "Check out the results of %1 over here:"
+ this->_shareUtils->shareText(qtTrId("#shareResultsLinkText").arg(compName) + " \n", url);
+}
+
+void BlueRockBackend::shareResultsAsPoster(QString url, QString compName) {
+ QString path = this->_shareUtils->getTemporaryFileLocationPath(); //QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+ if (!QDir(path).exists()) {
+ if (QDir("").mkpath(path)) {
+ qDebug() << "Created app data /files directory. " << path;
+ } else {
+ qWarning() << "Failed to create app data /files directory. " << path;
+ return;
+ }
+ }
+
+ QString rawCompName = compName;
+ QString escapedCompName = compName.replace("/", " ");
+ path += "/" + escapedCompName + ".pdf";
+
+ QFile file(path);
+ file.remove();
+ if(!file.open(QIODevice::ReadWrite)) {
+ qWarning("Could not open File for writing!!");
+ return;
+ }
+
+ // dimensions
+ QRect qrCodeRect = QRect(414, 414, 1650, 1650);
+
+ int compNameTextLineHeight = 64;
+ QFont compNameTextFont("OpenSans-Light");
+ compNameTextFont.setPixelSize(compNameTextLineHeight);
+ QRect compNameTextRect = QRect(
+ 324,
+ 2500,
+ 1835,
+ 150
+ );
+
+ QPdfWriter writer(&file);
+ writer.setPageSize(QPageSize(QPageSize::A4));
+ writer.setPageMargins(QMargins(0, 0, 0, 0));
+ writer.setResolution(300);
+
+ QPainter painter(&writer);
+ QPixmap posterTemplate(":/PosterTemplate.png");
+ painter.drawPixmap(0,0, writer.width(), writer.height(), posterTemplate);
+
+ QPixmap barcode;
+ QZXingEncoderConfig encoderConfig(QZXing::EncoderFormat_QR_CODE, qrCodeRect.size(), QZXing::EncodeErrorCorrectionLevel_H, false, false);
+ barcode.convertFromImage(QZXing::encodeData(url, encoderConfig));
+ painter.drawPixmap(qrCodeRect, barcode);
+
+ painter.setFont(compNameTextFont);
+ painter.setPen(Qt::black);
+ painter.drawText(compNameTextRect, Qt::AlignLeft|Qt::AlignBottom|Qt::TextWordWrap, rawCompName);
+
+ painter.end();
+ file.close();
+
+ this->_shareUtils->sendFile(path, escapedCompName, "application/pdf", 1);
+}
+
+bool BlueRockBackend::isCameraPermissionGranted() {
+#ifdef Q_OS_ANDROID
+ QtAndroid::PermissionResult cameraAccess = QtAndroid::checkPermission("android.permission.CAMERA");
+ return cameraAccess == QtAndroid::PermissionResult::Granted;
+#elif defined Q_OS_IOS
+ return this->_iosPermissionUtils->isCameraPermissionGranted();
+#else
+ return true;
+#endif
+}
+
+bool BlueRockBackend::requestCameraPermission() {
+ if(this->isCameraPermissionGranted())
+ return true;
+
+#ifdef Q_OS_ANDROID
+ // try to get permission
+ QtAndroid::PermissionResultMap resultMap = QtAndroid::requestPermissionsSync({"android.permission.CAMERA"});
+ bool resultBool = true;
+ for(QtAndroid::PermissionResult result : resultMap) {
+ if(result != QtAndroid::PermissionResult::Granted) {
+ resultBool = false;
+ }
+ }
+
+ if(resultBool) {
+ return true;
+ }
+
+ // getting permission the traditional way failed -> open the settings app
+ QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;");
+ if (activity.isValid())
+ {
+ // get the package name
+ QAndroidJniObject context = QtAndroid::androidContext();
+ QAndroidJniObject applicationPackageName = context.callObjectMethod("getPackageName");
+
+ QAndroidJniObject param = QAndroidJniObject::fromString("package:" + applicationPackageName.toString());
+
+ // Equivalent to Jave code: 'Uri uri = Uri::parse("...");'
+ QAndroidJniObject uri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", param.object());
+ if (!uri.isValid()) {
+ qWarning("ERROR: Unable to create Uri object");
+ return false;
+ }
+ QAndroidJniObject packageName = QAndroidJniObject::fromString("android.settings.APPLICATION_DETAILS_SETTINGS");
+
+ QAndroidJniObject intent("android/content/Intent","(Ljava/lang/String;)V", packageName.object());
+ if (!intent.isValid()) {
+ qWarning("ERROR: Unable to create Intent object");
+ return false;
+ }
+ intent.callObjectMethod("addCategory", "(Ljava/lang/String;)Landroid/content/Intent;", QAndroidJniObject::fromString("android.intent.category.DEFAULT").object());
+ intent.callObjectMethod("setData", "(Landroid/net/Uri;)Landroid/content/Intent;", uri.object());
+
+ activity.callMethod("startActivity","(Landroid/content/Intent;)V",intent.object());
+ }
+ else {
+ qWarning() << "ERROR: Activity not valid!";
+ return false;
+ }
+
+ return false;
+#elif defined Q_OS_IOS
+ return this->_iosPermissionUtils->requestCameraPermission();
+#else
+ return true;
+#endif
+
+}
+
+// ------------------------
+// --- Helper functions ---
+// ------------------------
+
+QVariantMap BlueRockBackend::_senddata(QUrl serviceUrl, QUrlQuery pdata)
+{
+ // create network manager
+ QNetworkAccessManager * networkManager = new QNetworkAccessManager();
+
+ QVariantMap ret; //this is a custom type to store the return-data
+
+ // Create network request
+ QNetworkRequest request(serviceUrl);
+ request.setHeader(QNetworkRequest::ContentTypeHeader,
+ "application/x-www-form-urlencoded");
+
+ //QSslConfiguration config = QSslConfiguration::defaultConfiguration();
+ //config.setProtocol(QSsl::TlsV1_2);
+ //request.setSslConfiguration(config);
+
+ //send a POST request with the given url and data to the server
+
+ QNetworkReply *reply;
+
+ if(pdata.isEmpty()) {
+ // if no post data is given -> send a GET request
+ reply = networkManager->get(request);
+ }
+ else {
+ // if post data is given -> send POST request
+ reply = networkManager->post(request, pdata.toString(QUrl::FullyEncoded).toUtf8());
+ }
+
+ // loop to wait until the request has finished before processing the data
+ QEventLoop loop;
+ // timer to cancel the request after 3 seconds
+ QTimer timer;
+ timer.setSingleShot(true);
+
+ // quit the loop when the request finised
+ loop.connect(networkManager, SIGNAL(finished(QNetworkReply*)), SLOT(quit()));
+ // or the timer timed out
+ loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
+ // start the timer
+ timer.start(10000);
+ // start the loop
+ loop.exec();
+
+ //get the status code
+ QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+
+ ret.insert("status", status_code.toInt());
+
+ //get the full text response
+ ret.insert("text", QString::fromUtf8(reply->readAll()));
+
+ // delete the reply object
+ delete reply;
+
+ // delete the newtwork access manager object
+ delete networkManager;
+
+ //return the data
+ return(ret);
+}
+
+#if defined(Q_OS_ANDROID)
+void BlueRockBackend::onApplicationStateChanged(Qt::ApplicationState applicationState)
+{
+ qDebug() << "S T A T E changed into: " << applicationState;
+ if(applicationState == Qt::ApplicationState::ApplicationSuspended) {
+ // nothing to do
+ return;
+ }
+ if(applicationState == Qt::ApplicationState::ApplicationActive) {
+ // if App was launched from VIEW or SEND Intent
+ // there's a race collision: the event will be lost,
+ // because App and UI wasn't completely initialized
+ // workaround: QShareActivity remembers that an Intent is pending
+ if(!_pendingIntentsChecked) {
+ _pendingIntentsChecked = true;
+ _shareUtils->checkPendingIntents(this->_shareUtils->getTemporaryFileLocationPath());
+ }
+ }
+}
+#endif
+
+// -------------------------
+// --- Functions for QML ---
+// -------------------------
diff --git a/sources/iospermissionutils.mm b/sources/iospermissionutils.mm
new file mode 100644
index 0000000..7935404
--- /dev/null
+++ b/sources/iospermissionutils.mm
@@ -0,0 +1,34 @@
+#import "../headers/iospermissionutils.h"
+#import
+#import
+
+IosPermissionUtils::IosPermissionUtils() : QObject(nullptr)
+{
+ this->_responseWaitLoop = new QEventLoop(this);
+}
+
+bool IosPermissionUtils::isCameraPermissionGranted() {
+ AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
+ return status == AVAuthorizationStatusAuthorized;
+}
+
+bool IosPermissionUtils::requestCameraPermission() {
+ if(this->isCameraPermissionGranted())
+ return true;
+
+ if ([AVCaptureDevice respondsToSelector:@selector(requestAccessForMediaType: completionHandler:)]) {
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
+ // Will get here on both iOS 7 & 8 even though camera permissions weren't required
+ // until iOS 8. So for iOS 7 permission will always be granted.
+ this->_responseWaitLoop->exit(granted);
+ }];
+ }
+
+ if(!this->_responseWaitLoop->exec()) {
+ // permission was not granted -> open settings
+ [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
+ return false;
+ }
+
+ return true;
+}
diff --git a/sources/main.cpp b/sources/main.cpp
index e099c5c..fb66458 100644
--- a/sources/main.cpp
+++ b/sources/main.cpp
@@ -20,10 +20,11 @@
#include
#include
#include
-#include
-#include
+#include
+#include
+#include "QZXing.h"
-#include "headers/serverconn.h"
+#include "headers/bluerockbackend.h"
#include "headers/appsettings.h"
int main(int argc, char *argv[])
@@ -33,12 +34,22 @@ int main(int argc, char *argv[])
QGuiApplication app(argc, argv);
- QQuickStyle::setStyle("Material");
- QIcon::setFallbackSearchPaths(QIcon::fallbackSearchPaths() << ":/resources/shared/icons");
- QIcon::setThemeName("bluerock");
+ // translation
+ QString localeName = QLocale::system().bcp47Name();
+ QTranslator* translator = new QTranslator(qApp);
- qmlRegisterType("com.itsblue.digitalRockRanking", 1, 0, "ServerConn");
- qmlRegisterType("com.itsblue.digitalRockRanking", 1, 0, "AppSettings");
+ // fallback to en!
+ if(!translator->load(":/" + localeName + ".qm"))
+ translator->load(":/en.qm");
+
+ QFontDatabase::addApplicationFont(":/fonts/OpenSans-Light.ttf");
+
+ QGuiApplication::installTranslator(translator);
+
+ QQuickStyle::setStyle("Material");
+
+ qmlRegisterType("de.itsblue.blueROCK", 1, 0, "BlueRockBackend");
+ qmlRegisterType("de.itsblue.blueROCK", 1, 0, "AppSettings");
QQmlApplicationEngine engine;
@@ -48,6 +59,9 @@ int main(int argc, char *argv[])
engine.rootContext()->setContextProperty("QT_DEBUG", false);
#endif
+ QZXing::registerQMLTypes();
+ QZXing::registerQMLImageProvider(engine);
+
engine.rootContext()->setContextProperty("APP_VERSION", APP_VERSION);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
diff --git a/sources/serverconn.cpp b/sources/serverconn.cpp
deleted file mode 100644
index 2279dec..0000000
--- a/sources/serverconn.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- blueROCK - for digital rock
- Copyright (C) 2019 Dorian Zedler
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
-
-#include "headers/serverconn.h"
-
-ServerConn::ServerConn(QObject *parent) : QObject(parent)
-{
-}
-
-QVariant ServerConn::getWidgetData(QVariantMap params) {
- QString requestUrl;
- if(params["nation"].toString() == "ICC") {
- requestUrl = "https://www.digitalrock.de/egroupware/ranking/json.php?";
- params["nation"] = "";
- }
- else if (params["nation"].toString() == "GER") {
- requestUrl = "https://www.digitalrock.de/egroupware/ranking/json.php?";
- }
- else if (params["nation"].toString() == "SUI") {
- requestUrl = "https://www.digitalrock.de/egroupware/ranking/json.php?";
- }
- else {
- params.remove("nation");
- requestUrl = "https://www.digitalrock.de/egroupware/ranking/json.php?";
- }
-
- for(QVariantMap::const_iterator iter = params.begin(); iter != params.end(); ++iter) {
- requestUrl += iter.key() + "=" + iter.value().toString() + "&";
- }
-
- requestUrl = requestUrl.left(requestUrl.length() - 1); // remove last '&'
-
- qDebug() << requestUrl;
-
- QVariantMap ret = this->senddata(QUrl(requestUrl));
-
- if(ret["status"] != 200) {
- // request was a failure
- return QVariantMap({{"status", ret["status"]}, {"data", ""}});
- }
-
- QJsonDocument jsonReply = QJsonDocument::fromJson(ret["text"].toString().toUtf8());
-
- QVariantMap data = {{"status", 200}, {"data", jsonReply.toVariant()}};
-
- return data;
-}
-
-// ------------------------
-// --- Helper functions ---
-// ------------------------
-
-QVariantMap ServerConn::senddata(QUrl serviceUrl, QUrlQuery pdata)
-{
- // create network manager
- QNetworkAccessManager * networkManager = new QNetworkAccessManager();
-
- QVariantMap ret; //this is a custom type to store the return-data
-
- // Create network request
- QNetworkRequest request(serviceUrl);
- request.setHeader(QNetworkRequest::ContentTypeHeader,
- "application/x-www-form-urlencoded");
-
- //QSslConfiguration config = QSslConfiguration::defaultConfiguration();
- //config.setProtocol(QSsl::TlsV1_2);
- //request.setSslConfiguration(config);
-
- //send a POST request with the given url and data to the server
-
- QNetworkReply *reply;
-
- if(pdata.isEmpty()) {
- // if no post data is given -> send a GET request
- reply = networkManager->get(request);
- }
- else {
- // if post data is given -> send POST request
- reply = networkManager->post(request, pdata.toString(QUrl::FullyEncoded).toUtf8());
- }
-
- // loop to wait until the request has finished before processing the data
- QEventLoop loop;
- // timer to cancel the request after 3 seconds
- QTimer timer;
- timer.setSingleShot(true);
-
- // quit the loop when the request finised
- loop.connect(networkManager, SIGNAL(finished(QNetworkReply*)), SLOT(quit()));
- // or the timer timed out
- loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
- // start the timer
- timer.start(10000);
- // start the loop
- loop.exec();
-
- //get the status code
- QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
-
- ret.insert("status", status_code.toInt());
-
- //get the full text response
- ret.insert("text", QString::fromUtf8(reply->readAll()));
-
- // delete the reply object
- delete reply;
-
- // delete the newtwork access manager object
- delete networkManager;
-
- //return the data
- return(ret);
-}
-
-// -------------------------
-// --- Functions for QML ---
-// -------------------------
-
-
-
diff --git a/sources/shareUtils/androidshareutils.cpp b/sources/shareUtils/androidshareutils.cpp
new file mode 100755
index 0000000..f3fdf64
--- /dev/null
+++ b/sources/shareUtils/androidshareutils.cpp
@@ -0,0 +1,379 @@
+// (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;
+}
+
+QString AndroidShareUtils::getTemporaryFileLocationPath() {
+ return QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).value(0) + "/temporaryFiles";
+}
+
+void AndroidShareUtils::shareText(const QString &text, const QUrl &url)
+{
+ QAndroidJniObject jsText = QAndroidJniObject::fromString(text + url.toString());
+ 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, "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, "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, "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, QString("File does not exist: %1").arg(myUrl));
+ }
+}
+
+void AndroidShareUtils::setOtherUrlReceived(const QString &url, const QString &scheme)
+{
+ if(url.isEmpty()) {
+ qWarning() << "setFileUrlReceived: we got an empty URL";
+ emit shareError(0, "Empty URL received");
+ return;
+ }
+ qDebug() << "AndroidShareUtils setOtherUrlReceived: we got the Other URL from JAVA: " << url;
+
+ emit otherUrlReceived(url, scheme);
+}
+
+void AndroidShareUtils::setFileReceivedAndSaved(const QString &url)
+{
+ if(url.isEmpty()) {
+ qWarning() << "setFileReceivedAndSaved: we got an empty URL";
+ emit shareError(0, "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, QString("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, "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_de_itsblue_blueROCK_MainActivity_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_de_itsblue_blueROCK_MainActivity_setOtherUrlReceived(JNIEnv *env,
+ jobject obj,
+ jstring url,
+ jstring scheme)
+{
+ const char *urlStr = env->GetStringUTFChars(url, NULL);
+ const char *schemeStr = env->GetStringUTFChars(scheme, NULL);
+ Q_UNUSED (obj)
+ AndroidShareUtils::getInstance()->setOtherUrlReceived(urlStr, schemeStr);
+ env->ReleaseStringUTFChars(url, urlStr);
+ env->ReleaseStringUTFChars(scheme, schemeStr);
+ return;
+}
+
+JNIEXPORT void JNICALL
+Java_de_itsblue_blueROCK_MainActivity_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_de_itsblue_blueROCK_MainActivity_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_de_itsblue_blueROCK_MainActivity_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/ios/docviewcontroller.mm b/sources/shareUtils/ios/docviewcontroller.mm
new file mode 100644
index 0000000..c4a5e36
--- /dev/null
+++ b/sources/shareUtils/ios/docviewcontroller.mm
@@ -0,0 +1,32 @@
+// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
+// my blog about Qt for mobile: http://j.mp/qt-x
+// see also /COPYRIGHT and /LICENSE
+
+#import "headers/shareUtils/ios/docviewcontroller.h"
+
+#include
+
+@interface DocViewController ()
+@end
+@implementation DocViewController
+#pragma mark -
+#pragma mark View Life Cycle
+- (void)viewDidLoad {
+ [super viewDidLoad];
+}
+#pragma mark -
+#pragma mark Document Interaction Controller Delegate Methods
+- (UIViewController *) documentInteractionControllerViewControllerForPreview: (UIDocumentInteractionController *) controller {
+#pragma unused (controller)
+ return self;
+}
+- (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controller
+{
+#pragma unused (controller)
+ qDebug() << "end preview";
+
+ self.mIosShareUtils->handleDocumentPreviewDone(self.requestId);
+
+ [self removeFromParentViewController];
+}
+@end
diff --git a/sources/shareUtils/ios/iosshareutils.mm b/sources/shareUtils/ios/iosshareutils.mm
new file mode 100755
index 0000000..a8bc5b6
--- /dev/null
+++ b/sources/shareUtils/ios/iosshareutils.mm
@@ -0,0 +1,156 @@
+// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
+// my blog about Qt for mobile: http://j.mp/qt-x
+// see also /COPYRIGHT and /LICENSE
+
+#import "headers/shareUtils/ios/iosshareutils.h"
+
+#import
+#import
+#import
+#import
+#import
+#import
+
+#import
+
+#import "headers/shareUtils/ios/docviewcontroller.h"
+
+IosShareUtils::IosShareUtils(QObject *parent) : PlatformShareUtils(parent)
+{
+ // Sharing Files from other iOS Apps I got the ideas and some code contribution from:
+ // Thomas K. Fischer (@taskfabric) - http://taskfabric.com - thx
+ QDesktopServices::setUrlHandler("file", this, "handleFileUrlReceived");
+ QDesktopServices::setUrlHandler("https", this, "handleHttpsUrlReceived");
+}
+
+bool IosShareUtils::checkMimeTypeView(const QString &mimeType) {
+#pragma unused (mimeType)
+ // dummi implementation on iOS
+ // MimeType not used yet
+ return true;
+}
+
+bool IosShareUtils::checkMimeTypeEdit(const QString &mimeType) {
+#pragma unused (mimeType)
+ // dummi implementation on iOS
+ // MimeType not used yet
+ return true;
+}
+
+void IosShareUtils::shareText(const QString &text, const QUrl &url) {
+
+ NSMutableArray *sharingItems = [NSMutableArray new];
+
+ if (!text.isEmpty()) {
+ [sharingItems addObject:text.toNSString()];
+ }
+
+ if (url.isValid()) {
+ [sharingItems addObject:url.toNSURL()];
+ }
+
+ // get the main window rootViewController
+ UIViewController *qtUIViewController = [[UIApplication sharedApplication].keyWindow rootViewController];
+
+ UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
+ if ( [activityController respondsToSelector:@selector(popoverPresentationController)] ) { // iOS8
+ activityController.popoverPresentationController.sourceView = qtUIViewController.view;
+ }
+ [qtUIViewController presentViewController:activityController animated:YES completion:nil];
+}
+
+// altImpl not used yet on iOS, on Android twi ways to use JNI
+void IosShareUtils::sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
+#pragma unused (title, mimeType)
+
+ NSString* nsFilePath = filePath.toNSString();
+ NSURL *nsFileUrl = [NSURL fileURLWithPath:nsFilePath];
+
+ static DocViewController* docViewController = nil;
+ if(docViewController!=nil)
+ {
+ [docViewController removeFromParentViewController];
+ [docViewController release];
+ }
+
+ UIDocumentInteractionController* documentInteractionController = nil;
+ documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:nsFileUrl];
+
+ UIViewController* qtUIViewController = [[[[UIApplication sharedApplication]windows] firstObject]rootViewController];
+ if(qtUIViewController!=nil)
+ {
+ docViewController = [[DocViewController alloc] init];
+
+ docViewController.requestId = requestId;
+ // we need this to be able to execute handleDocumentPreviewDone() method,
+ // when preview was finished
+ docViewController.mIosShareUtils = this;
+
+ [qtUIViewController addChildViewController:docViewController];
+ documentInteractionController.delegate = docViewController;
+ // [documentInteractionController presentPreviewAnimated:YES];
+ if(![documentInteractionController presentPreviewAnimated:YES])
+ {
+ emit shareError(0, tr("No App found to open: %1").arg(filePath));
+ }
+ }
+}
+
+
+void IosShareUtils::viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
+#pragma unused (title, mimeType)
+
+ sendFile(filePath, title, mimeType, requestId);
+}
+
+void IosShareUtils::editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
+#pragma unused (title, mimeType)
+
+ sendFile(filePath, title, mimeType, requestId);
+}
+
+void IosShareUtils::handleDocumentPreviewDone(const int &requestId)
+{
+ // documentInteractionControllerDidEndPreview
+ qDebug() << "handleShareDone: " << requestId;
+ emit shareFinished(requestId);
+}
+
+void IosShareUtils::handleFileUrlReceived(const QUrl &url)
+{
+ QString incomingUrl = url.toString();
+ if(incomingUrl.isEmpty()) {
+ qWarning() << "setFileUrlReceived: we got an empty URL";
+ emit shareError(0, tr("Empty URL received"));
+ return;
+ }
+ qDebug() << "IosShareUtils setFileUrlReceived: we got the File URL from iOS: " << incomingUrl;
+ QString myUrl;
+ if(incomingUrl.startsWith("file://")) {
+ myUrl= incomingUrl.right(incomingUrl.length()-7);
+ qDebug() << "QFile needs this URL: " << myUrl;
+ } else {
+ myUrl= incomingUrl;
+ }
+
+ // 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 IosShareUtils::handleHttpsUrlReceived(const QUrl &url)
+{
+ if(url.isEmpty()) {
+ qWarning() << "handleHttpsUrlReceived: we got an empty URL";
+ emit shareError(0, tr("Empty URL received"));
+ return;
+ }
+ qDebug() << "IosShareUtils handleHttpsUrlReceived: we got the Other URL from IOS: " << url;
+
+ emit otherUrlReceived(url.toString(), "https");
+}
diff --git a/sources/shareUtils/platformshareutils.cpp b/sources/shareUtils/platformshareutils.cpp
new file mode 100644
index 0000000..8ccc3cf
--- /dev/null
+++ b/sources/shareUtils/platformshareutils.cpp
@@ -0,0 +1,41 @@
+#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;
+}
+QString PlatformShareUtils::getTemporaryFileLocationPath() {
+ return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+}
+void PlatformShareUtils::shareText(const QString &text, const QUrl &url) {
+ qDebug() << text << url;
+}
+void PlatformShareUtils::sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
+ qDebug() << filePath << " - " << title << "requestId " << requestId << " - " << mimeType << "altImpl? ";
+ QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
+}
+void PlatformShareUtils::viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
+ qDebug() << filePath << " - " << title << " requestId: " << requestId << " - " << mimeType << "altImpl? ";
+ QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
+}
+void PlatformShareUtils::editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) {
+ qDebug() << filePath << " - " << title << " requestId: " << requestId << " - " << mimeType << "altImpl? ";
+ QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
+}
+
+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..3b4a9ec
--- /dev/null
+++ b/sources/shareUtils/shareutils.cpp
@@ -0,0 +1,124 @@
+// (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 "shareUtils/ios/iosshareutils.h"
+#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::otherUrlReceived, this, &ShareUtils::onOtherUrlReceived);
+ 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);
+}
+
+QString ShareUtils::getTemporaryFileLocationPath()
+{
+ return mPlatformShareUtils->getTemporaryFileLocationPath();
+}
+
+void ShareUtils::shareText(const QString &text, const QUrl &url)
+{
+ mPlatformShareUtils->shareText(text, url);
+}
+
+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::onOtherUrlReceived(QString url, QString scheme)
+{
+ emit otherUrlReceived(url, scheme);
+}
+
+void ShareUtils::onFileReceivedAndSaved(QString url)
+{
+ emit fileReceivedAndSaved(url);
+}
+