diff --git a/CHANGELOG.md b/CHANGELOG.md index 52d3360..32c974b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ 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). +## [1.1.0] - unreleased +### Hinzugefügt +- PDF-Taste oben links in der Vertretungsplan-Ansicht, um den Vertretungsplan als PDF anzusehen +- Fehlermeldung "Verarbeitungsfehler" mit Taste zum Ansehen der PDF Datei + +### Entfernt +- ständige Abfragen im Hintergrund, die sehr viel Datenvolumen verbrauchten + +### Geändert +- einige Icons +- pull-to-refresh indikator + ## [1.0.2] - 2019-03-14 ### Hinzugefügt - Dunkler Modus diff --git a/android-sources/AndroidManifest.xml b/android-sources/AndroidManifest.xml index 27552d0..8d01ef3 100644 --- a/android-sources/AndroidManifest.xml +++ b/android-sources/AndroidManifest.xml @@ -1,5 +1,5 @@ - + - - @@ -83,6 +83,10 @@ + + + + diff --git a/android-sources/build.gradle b/android-sources/build.gradle new file mode 100644 index 0000000..5db707f --- /dev/null +++ b/android-sources/build.gradle @@ -0,0 +1,58 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.2.0' + } +} + +repositories { + google() + jcenter() +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) + compile 'com.android.support:support-v4:25.3.1' +} + +android { + /******************************************************* + * The following variables: + * - androidBuildToolsVersion, + * - androidCompileSdkVersion + * - qt5AndroidDir - holds the path to qt android files + * needed to build any Qt application + * on Android. + * + * are defined in gradle.properties file. This file is + * updated by QtCreator and androiddeployqt tools. + * Changing them manually might break the compilation! + *******************************************************/ + + compileSdkVersion androidCompileSdkVersion.toInteger() + + buildToolsVersion androidBuildToolsVersion + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] + aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] + res.srcDirs = [qt5AndroidDir + '/res', 'res'] + resources.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } + + lintOptions { + abortOnError false + } +} diff --git a/android-sources/gradle/wrapper/gradle-wrapper.jar b/android-sources/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/android-sources/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android-sources/gradle/wrapper/gradle-wrapper.properties b/android-sources/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bf3de21 --- /dev/null +++ b/android-sources/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android-sources/gradlew b/android-sources/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/android-sources/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/android-sources/gradlew.bat b/android-sources/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/android-sources/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android-sources/libs/arm64-v8a/lib/libcrypto.a b/android-sources/libs/arm64-v8a/lib/libcrypto.a new file mode 100644 index 0000000..9c167ac Binary files /dev/null and b/android-sources/libs/arm64-v8a/lib/libcrypto.a differ diff --git a/android-sources/libs/arm64-v8a/lib/libcrypto.so.1.1 b/android-sources/libs/arm64-v8a/lib/libcrypto.so.1.1 new file mode 100755 index 0000000..33a45fe Binary files /dev/null and b/android-sources/libs/arm64-v8a/lib/libcrypto.so.1.1 differ diff --git a/android-sources/libs/arm64-v8a/lib/libssl.a b/android-sources/libs/arm64-v8a/lib/libssl.a new file mode 100644 index 0000000..2f821a9 Binary files /dev/null and b/android-sources/libs/arm64-v8a/lib/libssl.a differ diff --git a/android-sources/libs/arm64-v8a/lib/libssl.so.1.1 b/android-sources/libs/arm64-v8a/lib/libssl.so.1.1 new file mode 100755 index 0000000..f1a9799 Binary files /dev/null and b/android-sources/libs/arm64-v8a/lib/libssl.so.1.1 differ diff --git a/android-sources/libs/arm64-v8a/lib/symlink-windows.bat b/android-sources/libs/arm64-v8a/lib/symlink-windows.bat new file mode 100644 index 0000000..9167249 --- /dev/null +++ b/android-sources/libs/arm64-v8a/lib/symlink-windows.bat @@ -0,0 +1,3 @@ +del libcrypto.so libssl.so +mklink /H libcrypto.so libcrypto.so.1.1 +mklink /H libssl.so libssl.so.1.1 \ No newline at end of file diff --git a/android-sources/libs/armeabi/lib/libcrypto.a b/android-sources/libs/armeabi/lib/libcrypto.a new file mode 100644 index 0000000..6910dea Binary files /dev/null and b/android-sources/libs/armeabi/lib/libcrypto.a differ diff --git a/android-sources/libs/armeabi/lib/libcrypto.so.1.1 b/android-sources/libs/armeabi/lib/libcrypto.so.1.1 new file mode 100755 index 0000000..6ee6d8c Binary files /dev/null and b/android-sources/libs/armeabi/lib/libcrypto.so.1.1 differ diff --git a/android-sources/libs/armeabi/lib/libssl.a b/android-sources/libs/armeabi/lib/libssl.a new file mode 100644 index 0000000..5cf6c76 Binary files /dev/null and b/android-sources/libs/armeabi/lib/libssl.a differ diff --git a/android-sources/libs/armeabi/lib/libssl.so.1.1 b/android-sources/libs/armeabi/lib/libssl.so.1.1 new file mode 100755 index 0000000..528975f Binary files /dev/null and b/android-sources/libs/armeabi/lib/libssl.so.1.1 differ diff --git a/android-sources/libs/armeabi/lib/symlink-windows.bat b/android-sources/libs/armeabi/lib/symlink-windows.bat new file mode 100644 index 0000000..9167249 --- /dev/null +++ b/android-sources/libs/armeabi/lib/symlink-windows.bat @@ -0,0 +1,3 @@ +del libcrypto.so libssl.so +mklink /H libcrypto.so libcrypto.so.1.1 +mklink /H libssl.so libssl.so.1.1 \ No newline at end of file diff --git a/android-sources/libs/x86/lib/libcrypto.a b/android-sources/libs/x86/lib/libcrypto.a new file mode 100644 index 0000000..74580cb Binary files /dev/null and b/android-sources/libs/x86/lib/libcrypto.a differ diff --git a/android-sources/libs/x86/lib/libcrypto.so.1.1 b/android-sources/libs/x86/lib/libcrypto.so.1.1 new file mode 100755 index 0000000..32c04fe Binary files /dev/null and b/android-sources/libs/x86/lib/libcrypto.so.1.1 differ diff --git a/android-sources/libs/x86/lib/libssl.a b/android-sources/libs/x86/lib/libssl.a new file mode 100644 index 0000000..459582d Binary files /dev/null and b/android-sources/libs/x86/lib/libssl.a differ diff --git a/android-sources/libs/x86/lib/libssl.so.1.1 b/android-sources/libs/x86/lib/libssl.so.1.1 new file mode 100755 index 0000000..a72c015 Binary files /dev/null and b/android-sources/libs/x86/lib/libssl.so.1.1 differ diff --git a/android-sources/libs/x86/lib/symlink-windows.bat b/android-sources/libs/x86/lib/symlink-windows.bat new file mode 100644 index 0000000..9167249 --- /dev/null +++ b/android-sources/libs/x86/lib/symlink-windows.bat @@ -0,0 +1,3 @@ +del libcrypto.so libssl.so +mklink /H libcrypto.so libcrypto.so.1.1 +mklink /H libssl.so libssl.so.1.1 \ No newline at end of file diff --git a/android-sources/libs/x86/libcrypto.so b/android-sources/libs/x86/libcrypto.so deleted file mode 100644 index a2e46cd..0000000 Binary files a/android-sources/libs/x86/libcrypto.so and /dev/null differ diff --git a/android-sources/libs/x86/libssl.so b/android-sources/libs/x86/libssl.so deleted file mode 100644 index 3f3032a..0000000 Binary files a/android-sources/libs/x86/libssl.so and /dev/null differ diff --git a/android-sources/libs/x86_64/lib/libcrypto.a b/android-sources/libs/x86_64/lib/libcrypto.a new file mode 100644 index 0000000..890cfd4 Binary files /dev/null and b/android-sources/libs/x86_64/lib/libcrypto.a differ diff --git a/android-sources/libs/x86_64/lib/libcrypto.so.1.1 b/android-sources/libs/x86_64/lib/libcrypto.so.1.1 new file mode 100755 index 0000000..2ee82a9 Binary files /dev/null and b/android-sources/libs/x86_64/lib/libcrypto.so.1.1 differ diff --git a/android-sources/libs/x86_64/lib/libssl.a b/android-sources/libs/x86_64/lib/libssl.a new file mode 100644 index 0000000..ec80614 Binary files /dev/null and b/android-sources/libs/x86_64/lib/libssl.a differ diff --git a/android-sources/libs/x86_64/lib/libssl.so.1.1 b/android-sources/libs/x86_64/lib/libssl.so.1.1 new file mode 100755 index 0000000..637d6e3 Binary files /dev/null and b/android-sources/libs/x86_64/lib/libssl.so.1.1 differ diff --git a/android-sources/libs/x86_64/lib/symlink-windows.bat b/android-sources/libs/x86_64/lib/symlink-windows.bat new file mode 100644 index 0000000..9167249 --- /dev/null +++ b/android-sources/libs/x86_64/lib/symlink-windows.bat @@ -0,0 +1,3 @@ +del libcrypto.so libssl.so +mklink /H libcrypto.so libcrypto.so.1.1 +mklink /H libssl.so libssl.so.1.1 \ No newline at end of file diff --git a/android-sources/res/drawable-hdpi/screen.png b/android-sources/res/drawable-hdpi/screen.png deleted file mode 100644 index 27bfc30..0000000 Binary files a/android-sources/res/drawable-hdpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-land-hdpi/screen.png b/android-sources/res/drawable-land-hdpi/screen.png deleted file mode 100644 index c9927dc..0000000 Binary files a/android-sources/res/drawable-land-hdpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-land-ldpi/screen.png b/android-sources/res/drawable-land-ldpi/screen.png deleted file mode 100644 index 190182d..0000000 Binary files a/android-sources/res/drawable-land-ldpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-land-mdpi/screen.png b/android-sources/res/drawable-land-mdpi/screen.png deleted file mode 100644 index e7c9b8b..0000000 Binary files a/android-sources/res/drawable-land-mdpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-land-xhdpi/screen.png b/android-sources/res/drawable-land-xhdpi/screen.png deleted file mode 100644 index 98eeec4..0000000 Binary files a/android-sources/res/drawable-land-xhdpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-land-xxhdpi/screen.png b/android-sources/res/drawable-land-xxhdpi/screen.png deleted file mode 100644 index df57ea0..0000000 Binary files a/android-sources/res/drawable-land-xxhdpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-land-xxxhdpi/screen.png b/android-sources/res/drawable-land-xxxhdpi/screen.png deleted file mode 100644 index d4be0e5..0000000 Binary files a/android-sources/res/drawable-land-xxxhdpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-land/screen.png b/android-sources/res/drawable-land/screen.png deleted file mode 100644 index c9927dc..0000000 Binary files a/android-sources/res/drawable-land/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-ldpi/screen.png b/android-sources/res/drawable-ldpi/screen.png deleted file mode 100644 index 2c8b88c..0000000 Binary files a/android-sources/res/drawable-ldpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-mdpi/screen.png b/android-sources/res/drawable-mdpi/screen.png deleted file mode 100644 index b5a46d7..0000000 Binary files a/android-sources/res/drawable-mdpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-xhdpi/screen.png b/android-sources/res/drawable-xhdpi/screen.png deleted file mode 100644 index ec22faa..0000000 Binary files a/android-sources/res/drawable-xhdpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-xxhdpi/screen.png b/android-sources/res/drawable-xxhdpi/screen.png deleted file mode 100644 index 6a1e046..0000000 Binary files a/android-sources/res/drawable-xxhdpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable-xxxhdpi/screen.png b/android-sources/res/drawable-xxxhdpi/screen.png deleted file mode 100644 index 5a2513d..0000000 Binary files a/android-sources/res/drawable-xxxhdpi/screen.png and /dev/null differ diff --git a/android-sources/res/drawable/screen.png b/android-sources/res/drawable/screen.png deleted file mode 100644 index 27bfc30..0000000 Binary files a/android-sources/res/drawable/screen.png and /dev/null differ diff --git a/android-sources/res/values/libs.xml b/android-sources/res/values/libs.xml new file mode 100644 index 0000000..4009a77 --- /dev/null +++ b/android-sources/res/values/libs.xml @@ -0,0 +1,25 @@ + + + + https://download.qt.io/ministro/android/qt5/qt-5.9 + + + + + + + + + + + + + + + + + + + + diff --git a/android-sources/res/xml/filepaths.xml b/android-sources/res/xml/filepaths.xml new file mode 100644 index 0000000..debf528 --- /dev/null +++ b/android-sources/res/xml/filepaths.xml @@ -0,0 +1,3 @@ + + + diff --git a/android-sources/src/de/itsblue/fannyapp/MainActivity.java b/android-sources/src/de/itsblue/fannyapp/MainActivity.java new file mode 100644 index 0000000..47dc8af --- /dev/null +++ b/android-sources/src/de/itsblue/fannyapp/MainActivity.java @@ -0,0 +1,39 @@ +package de.itsblue.fannyapp; + +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 + // + public static native void fireActivityResult(int requestCode, int resultCode); + + // 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) { + // 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); + } +} // class QShareActivity + diff --git a/android-sources/src/org/ekkescorner/utils/QShareUtils.java b/android-sources/src/org/ekkescorner/utils/QShareUtils.java new file mode 100755 index 0000000..044e6c9 --- /dev/null +++ b/android-sources/src/org/ekkescorner/utils/QShareUtils.java @@ -0,0 +1,199 @@ +// (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.fannyapp.fileprovider"; + + protected QShareUtils() + { + //Log.d("ekkescorner", "QShareUtils()"); + } + + // 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; + } + + 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); + } + +} diff --git a/fannyapp.pro b/fannyapp.pro index 464ecc8..3dca45e 100644 --- a/fannyapp.pro +++ b/fannyapp.pro @@ -20,6 +20,7 @@ TARGET = fannyapp ICON = shared/graphics/favicon.icns SOURCES += \ + sources/filehelper.cpp \ sources/serverconn.cpp \ sources/main.cpp \ sources/appsettings.cpp \ @@ -29,6 +30,7 @@ SOURCES += \ sources/appstyle.cpp HEADERS += \ + headers/filehelper.h \ headers/serverconn.h \ headers/appsettings.h \ headers/foodplanmodel.h \ @@ -68,5 +70,8 @@ ios { DISTFILES += \ android-sources/AndroidManifest.xml \ - CHANGELOG.md + android-sources/build.gradle \ + CHANGELOG.md \ + android-sources/src/de/itsblue/fannyapp/MainActivity.java \ + android-sources/src/org/ekkescorner/utils/QShareUtils.java diff --git a/headers/filehelper.h b/headers/filehelper.h new file mode 100644 index 0000000..2c32d95 --- /dev/null +++ b/headers/filehelper.h @@ -0,0 +1,50 @@ +#ifndef FILEHELPER_H +#define FILEHELPER_H + +#include +#include + +#if defined(Q_OS_IOS) + mPlatformShareUtils = new IosShareUtils(this); +#elif defined(Q_OS_ANDROID) +#include +#include +#else +#include +#include +#endif + +class FileHelper : public QObject + #if defined(Q_OS_ANDROID) + , public QAndroidActivityResultReceiver + #endif +{ + Q_OBJECT +public: + explicit FileHelper(QObject *parent = nullptr); + + void viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId); +#if defined(Q_OS_ANDROID) + void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data); + void onActivityResult(int requestCode, int resultCode); + static FileHelper* getInstance(); +#endif + +private: +#if defined(Q_OS_IOS) +#elif defined(Q_OS_ANDROID) + void processActivityResult(int requestCode, int resultCode); + static FileHelper* mInstance; +#else +#endif + +signals: + void shareEditDone(int requestCode); + void shareFinished(int requestCode); + void shareNoAppAvailable(int requestCode); + void shareError(int requestCode, QString message); + +public slots: +}; + +#endif // FILEHELPER_H diff --git a/headers/serverconn.h b/headers/serverconn.h index 16474ff..90a2a6b 100644 --- a/headers/serverconn.h +++ b/headers/serverconn.h @@ -29,21 +29,17 @@ #include #include "headers/appsettings.h" +#include "headers/filehelper.h" #ifdef Q_OS_ANDROID #include #endif - -typedef struct strReturnData{ - int status_code; - QString text; -}ReturnData_t; - class ServerConn : public QObject { Q_OBJECT Q_PROPERTY(QString state READ getState NOTIFY stateChanged) + Q_PROPERTY(double downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged) private: QString state; @@ -51,12 +47,18 @@ private: QString username; QString password; - ReturnData_t senddata(QUrl serviceUrl, QUrlQuery postData); + QVariantMap senddata(QUrl serviceUrl, QUrlQuery postData, bool raw = false); QList apiVersion = {0,2,1}; + FileHelper * fileHelper; + QString mDocumentsWorkPath; + + double downloadProgress; + private slots: void setState(QString state); + void updateDownloadProgress(qint64 read, qint64 total); public: explicit ServerConn(QObject *parent = nullptr); @@ -66,12 +68,15 @@ public slots: Q_INVOKABLE int login(QString username, QString password, bool permanent); Q_INVOKABLE int logout(); Q_INVOKABLE int getFoodPlan(); + Q_INVOKABLE int openEventPdf(QString day); Q_INVOKABLE int getEvents(QString day); + Q_INVOKABLE double getDownloadProgress(); Q_INVOKABLE QString getState(); signals: void stateChanged(QString newState); + void downloadProgressChanged(); public: QList m_weekplan; diff --git a/qml/Components/EventView.qml b/qml/Components/EventView.qml index 6d77616..4d8676a 100644 --- a/qml/Components/EventView.qml +++ b/qml/Components/EventView.qml @@ -29,7 +29,6 @@ FannyDataListView { id: foodPlanModel } - delegate: Button { id: delegate diff --git a/qml/Components/FannyDataListView.qml b/qml/Components/FannyDataListView.qml index 6affe30..f13bd6a 100644 --- a/qml/Components/FannyDataListView.qml +++ b/qml/Components/FannyDataListView.qml @@ -24,6 +24,7 @@ ListView { id: control property int status: -1 + property var optionButtonFunction: undefined signal refresh() @@ -56,15 +57,23 @@ ListView { InfoArea { id: infoArea - anchors { - left: parent.left - right: parent.right - top: parent.top - margins: app.landscape() ? parent.width * 0.4:parent.width * 0.3 - topMargin: parent.height*( status === 901 ? 0.6:0.5) - height * 0.8 - } + z: 0 + + anchors.fill: parent excludedCodes: [200, 902] errorCode: control.status + optionButtonFunction: control.optionButtonFunction + } + + PullRefresher{ + target: control + + backgroundColor: app.style.style.buttonColor + pullIndicatorColor: app.style.style.textColor + + preRefreshDelay: 300 + + refreshPosition: height * 1.3 } } diff --git a/qml/Components/InfoArea.qml b/qml/Components/InfoArea.qml index 6d8be97..e615289 100644 --- a/qml/Components/InfoArea.qml +++ b/qml/Components/InfoArea.qml @@ -29,44 +29,48 @@ Item { property int errorCode: -1 property var excludedCodes: [] + property var optionButtonFunction: undefined + visible: !(excludedCodes.indexOf(errorCode) >= 0) - height: childrenRect.height + Column { + anchors.centerIn: parent - Rectangle { - - radius: height * 0.5 - width: parent.width - height: width - - color: "transparent" - border.width: 5 - border.color: infoArea.alertLevel > 0 ? infoArea.alertLevel > 1 ? "red":"grey" : "green" + width: parent.width * 0.8 opacity: infoArea.errorCode !== 200 && infoArea.errorCode !== (-1) ? 1:0 - Behavior on opacity { - NumberAnimation { - duration: 500 - } - } + spacing: 20 + + Rectangle { + anchors { + horizontalCenter: parent.horizontalCenter + } + + radius: height * 0.5 + width: app.landscape() ? infoArea.height * 0.4 : parent.width * 0.5 + height: width + + color: "transparent" + border.width: 5 + border.color: ["green", "grey", "orange", "red"][infoArea.alertLevel] + + Label { + anchors.centerIn: parent + font.pixelSize: parent.height * 0.8 + text: infoArea.alertLevel > 1 ? "!":"i" + color: parent.border.color + } - Label { - anchors.centerIn: parent - font.pixelSize: parent.height * 0.8 - text: infoArea.alertLevel > 1 ? "!":"i" - color: infoArea.alertLevel > 0 ? infoArea.alertLevel > 1 ? "red":"grey" : "green" } Label { id: errorShortDescription anchors { horizontalCenter: parent.horizontalCenter - top: parent.bottom - margins: parent.height * 0.1 } - width: app.width * 0.8 + width: parent.width wrapMode: Label.Wrap @@ -80,11 +84,9 @@ Item { id: errorLongDescription anchors { horizontalCenter: parent.horizontalCenter - top: errorShortDescription.bottom - margins: parent.height * 0.1 } - width: app.width * 0.8 + width: parent.width wrapMode: Label.Wrap @@ -92,6 +94,28 @@ Item { text: app.getErrorInfo(infoArea.errorCode)[2] } + + Button { + id: optionButton + + anchors { + horizontalCenter: parent.horizontalCenter + } + + visible: text !== "" && infoArea.optionButtonFunction !== undefined + + text: app.getErrorInfo(infoArea.errorCode)[3] + + onClicked: { + infoArea.optionButtonFunction() + } + } + + Behavior on opacity { + NumberAnimation { + duration: 500 + } + } } } diff --git a/qml/Components/ProgressCircle.qml b/qml/Components/ProgressCircle.qml new file mode 100644 index 0000000..26268d3 --- /dev/null +++ b/qml/Components/ProgressCircle.qml @@ -0,0 +1,90 @@ +import QtQuick 2.0 +import QtQml 2.2 + +Item { + id: root + + width: size + height: size + + property int size: 200 // The size of the circle in pixel + property real arcBegin: 0 // start arc angle in degree + property real arcEnd: 270 // end arc angle in degree + property real arcOffset: 0 // rotation + property bool isPie: false // paint a pie instead of an arc + property bool showBackground: false // a full circle as a background of the arc + property real lineWidth: 20 // width of the line + property string colorCircle: "#CC3333" + property string colorBackground: "#779933" + + property alias beginAnimation: animationArcBegin.enabled + property alias endAnimation: animationArcEnd.enabled + + property int animationDuration: 20 + + onArcBeginChanged: canvas.requestPaint() + onArcEndChanged: canvas.requestPaint() + + Behavior on arcBegin { + id: animationArcBegin + enabled: true + NumberAnimation { + duration: root.animationDuration + easing.type: Easing.InOutCubic + } + } + + Behavior on arcEnd { + id: animationArcEnd + enabled: true + NumberAnimation { + duration: root.animationDuration + easing.type: Easing.InOutCubic + } + } + + Canvas { + id: canvas + anchors.fill: parent + rotation: -90 + parent.arcOffset + + onPaint: { + var ctx = getContext("2d") + var x = width / 2 + var y = height / 2 + var start = Math.PI * (parent.arcBegin / 180) + var end = Math.PI * (parent.arcEnd / 180) + ctx.reset() + + if (root.isPie) { + if (root.showBackground) { + ctx.beginPath() + ctx.fillStyle = root.colorBackground + ctx.moveTo(x, y) + ctx.arc(x, y, width / 2, 0, Math.PI * 2, false) + ctx.lineTo(x, y) + ctx.fill() + } + ctx.beginPath() + ctx.fillStyle = root.colorCircle + ctx.moveTo(x, y) + ctx.arc(x, y, width / 2, start, end, false) + ctx.lineTo(x, y) + ctx.fill() + } else { + if (root.showBackground) { + ctx.beginPath(); + ctx.arc(x, y, (width / 2) - parent.lineWidth / 2, 0, Math.PI * 2, false) + ctx.lineWidth = root.lineWidth + ctx.strokeStyle = root.colorBackground + ctx.stroke() + } + ctx.beginPath(); + ctx.arc(x, y, (width / 2) - parent.lineWidth / 2, start, end, false) + ctx.lineWidth = root.lineWidth + ctx.strokeStyle = root.colorCircle + ctx.stroke() + } + } + } +} diff --git a/qml/Components/PullRefresher.qml b/qml/Components/PullRefresher.qml new file mode 100644 index 0000000..e24560a --- /dev/null +++ b/qml/Components/PullRefresher.qml @@ -0,0 +1,327 @@ +/* + 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 . +*/ + +import QtQuick 2.9 +import QtQuick.Controls 2.4 +import QtGraphicalEffects 1.0 + +Item { + id: control + + state: "idle" + + property var target // targeted ListView + + property bool autoConfigureTarget: true // should the target be automaticaly be configured? + + property int postRefreshDelay: 1000 // delay after reload funcion has finished + property int preRefreshDelay: 1000 // delay before reload funcion is called + + property int refreshPosition: height * 1.2 // position of the item when refreshing + property int dragOutPosition: height * 1.8 // maximum drag out + + property double dragRefreshPositionMultiplier: 0.5 // 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 { + RectangularGlow { + anchors.fill: backgroundRe + + scale: 0.8 * backgroundRe.scale + cornerRadius: backgroundRe.radius + color: "black" + + glowRadius: 0.001 + spread: 0.2 + } + + Rectangle { + id: backgroundRe + + anchors.fill: parent + + radius: width * 0.5 + color: control.backgroundColor + } + } + property Component busyIndicator: BusyIndicator { running: true } + property Component pullIndicator: Canvas { + + property double drawProgress: control.dragProgress + + rotation: drawProgress > control.dragRefreshPositionMultiplier ? 180:0 + + onDrawProgressChanged: { + requestPaint() + } + + onPaint: { + var ctx = getContext("2d"); + + var topMargin = height * 0.1 + var bottomMargin = topMargin + var rightMargin = 0 + var leftMargin = 0 + + var arrowHeight = height - topMargin - bottomMargin + + var peakHeight = arrowHeight * 0.35 + var peakWidth = peakHeight + + var lineWidth = 2 + + var progress = drawProgress * 1 / control.dragRefreshPositionMultiplier > 1 ? 1 : drawProgress * 1 / control.dragRefreshPositionMultiplier + // modify all values to math the progress + + arrowHeight = arrowHeight * progress + if(progress > 0.3){ + peakHeight = peakHeight * (progress - 0.3) * 1/0.7 + peakWidth = peakWidth * (progress - 0.3) * 1/0.7 + } + else { + peakHeight = 0 + peakWidth = 0 + } + + // clear canvas + ctx.reset() + + ctx.lineWidth = lineWidth; + ctx.strokeStyle = control.pullIndicatorColor; + + // middle line + ctx.moveTo(width/2, topMargin); + ctx.lineTo(width/2, arrowHeight + topMargin); + + // right line + ctx.moveTo(width/2 - lineWidth * 0.3, arrowHeight + topMargin); + ctx.lineTo(width/2 + peakWidth,arrowHeight + topMargin - peakHeight); + // left line + ctx.moveTo(width/2 + lineWidth * 0.3, arrowHeight + topMargin); + ctx.lineTo(width/2 - peakWidth,arrowHeight + topMargin - peakHeight); + + ctx.stroke(); + } + + Behavior on rotation { + NumberAnimation { + duration: 100 + } + } + + } + + signal refreshRequested + + // internal properties + property int minimumPosition: 0 + property int maximumPosition: 0 + property int userPosition: 0 + property int position: Math.max( minimumPosition, Math.min(maximumPosition, userPosition)) + + height: 50 + width: height + + Component.onCompleted: { + if(control.autoConfigureTarget){ + target.boundsBehavior = Flickable.DragOverBounds + target.boundsMovement = Flickable.StopAtBounds + } + } + + function refresh() { + control.refreshRequested() + postRefreshTimer.start() + } + + anchors { + top: control.target.top + horizontalCenter: control.target.horizontalCenter + topMargin: control.position - height + } + + Connections { + target: control.target + onDragEnded: { + if(userPosition >= control.dragOutPosition * control.dragRefreshPositionMultiplier){ + control.state = "refreshing" + preRefreshTimer.start() + } + } + } + + Loader { + id: backgroundLd + + anchors.fill: parent + + sourceComponent: control.background + } + + Loader { + id: pullIndicatorLd + + anchors.centerIn: parent + + height: parent.height * 0.6 + width: height + + rotation: 180 + + sourceComponent: control.pullIndicator + } + + Loader { + id: busyIndicatorLd + + anchors.centerIn: parent + + height: parent.height * 0.7 + width: height + + opacity: 0 + + sourceComponent: control.busyIndicator + } + + Timer { + id: preRefreshTimer + interval: control.preRefreshDelay <= 0 ? 1:control.preRefreshDelay + running: false + repeat: false + onTriggered: { + control.refresh() + } + } + + Timer { + id: postRefreshTimer + interval: control.postRefreshDelay <= 0 ? 1:control.postRefreshDelay + running: false + repeat: false + onTriggered: { + control.state = "hidden" + } + } + + Behavior on minimumPosition { + enabled: !control.target.dragging && state !== "idle" + NumberAnimation { + duration: 100 + } + } + + states: [ + State { + name: "idle" + + PropertyChanges { + target: control + minimumPosition: userPosition > maximumPosition ? maximumPosition:userPosition + userPosition: -1 / (Math.abs( (target.verticalOvershoot > 0 ? 0:target.verticalOvershoot) * 0.001 + 0.003 ) + 1 / control.dragOutPosition * 0.001) + control.dragOutPosition // Math.abs( target.verticalOvershoot ) + maximumPosition: control.dragOutPosition + } + + PropertyChanges { + target: pullIndicatorLd + rotation: 0 + } + }, + State { + name: "refreshing" + PropertyChanges { + target: control + minimumPosition: control.refreshPosition + userPosition: 0 + maximumPosition: control.refreshPosition + + } + + PropertyChanges { + target: pullIndicatorLd + opacity: 0 + } + + PropertyChanges { + target: busyIndicatorLd + opacity: 1 + } + }, + State { + name: "hidden" + PropertyChanges { + target: control + minimumPosition: control.refreshPosition + userPosition: 0 + maximumPosition: control.refreshPosition + scale: 0 + } + + PropertyChanges { + target: pullIndicatorLd + opacity: 0 + } + + PropertyChanges { + target: busyIndicatorLd + opacity: 1 + } + } + ] + + transitions: [ + Transition { + NumberAnimation { + duration: 100 + properties: "rotation, opacity" + } + }, + + Transition { + from: "refreshing" + to: "hidden" + + PauseAnimation { + duration: 200 + } + + NumberAnimation { + duration: 200 + properties: "scale" + } + + onRunningChanged: { + if(control.state === "hidden" && !running){ + control.state = "idle" + } + } + }, + + Transition { + from: "hidden" + to: "idle" + } + + ] + +} diff --git a/qml/Forms/EventForm.qml b/qml/Forms/EventForm.qml index 7efd109..05fdcc4 100644 --- a/qml/Forms/EventForm.qml +++ b/qml/Forms/EventForm.qml @@ -17,8 +17,10 @@ */ import QtQuick 2.9 +import Backend 1.0 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 +import QtQuick.Controls.Material 2.0 import "../Components" Page { @@ -35,56 +37,169 @@ Page { onOpened: {} + function pdfAction() { + busyDialog.open() + serverConn.openEventPdf(day) + busyDialog.close() + } + Loader { id: pageLoader - property string newSource: "" + property var newSourceComponent - onNewSourceChanged: { + anchors.fill: parent + sourceComponent: loadingFormComp + + onNewSourceComponentChanged: { oldItemAnimation.start() } - anchors.fill: parent - source: "./LoadingForm.qml" - - onSourceChanged: { - pageLoader.item.status = root.status - newItemAnimation.start() + onSourceComponentChanged: { + if(pageLoader.item !== null) { + pageLoader.item.status = root.status + newItemAnimation.start() + } } - NumberAnimation { + + + ParallelAnimation { id: newItemAnimation - target: pageLoader.item - property: "opacity" - from: 0 - to: 1 - duration: 200 - easing.type: Easing.InExpo + + NumberAnimation { + target: pageLoader.item + property: "opacity" + from: 0 + to: 1 + duration: 300 + easing.type: Easing.InExpo + } + + NumberAnimation { + target: pageLoader.item + property: "scale" + from: 0.98 + to: 1 + duration: 300 + easing.type: Easing.InExpo + } } - NumberAnimation { + ParallelAnimation { id: oldItemAnimation - target: pageLoader.item - property: "opacity" - from: 1 - to: 0 - duration: 200 - easing.type: Easing.InExpo + + NumberAnimation { + target: pageLoader.item + property: "opacity" + from: 1 + to: 0 + duration: 200 + easing.type: Easing.InExpo + } onRunningChanged: { if(!running){ - pageLoader.source = pageLoader.newSource + pageLoader.sourceComponent = pageLoader.newSourceComponent } } } - Connections { - target: pageLoader.item - onRefresh: { - pageLoader.newSource = "./LoadingForm.qml" - loadTimer.start() + Component { + id: eventListComp + + FannyDataListView { + id: eventList + + status: 900 + + optionButtonFunction: function() { + busyDialog.open() + serverConn.openEventPdf(day) + busyDialog.close() + } + + model: EventModel { + id: foodPlanModel + } + + delegate: Button { + id: delegate + + width: eventList.width + height: contentCol.height + 10 + + z: 100 + + Column { + id: contentCol + + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: 10 + } + + height: childrenRect.height + 10 + + spacing: 1 + + Label { + id: gradeLa + // label for the grade + + font.bold: true + font.pixelSize: hourReplaceSubjectRoomLa.font.pixelSize * 1.5 + + width: parent.width - 10 + wrapMode: Label.Wrap + + text: grade + } + + Label { + id: hourReplaceSubjectRoomLa + // label for the hour, replacement, subject and room + + width: parent.width - 10 + wrapMode: Label.Wrap + + text: hour + ( replace === "" ? "": ( " | " + + replace + ( subject === "" ? "": ( " | " + + subject + ( room === "" ? "": ( " | " + + room ) ) ) ) ) ) + } + + Label { + id: toTextLa + // label for the new room (to) and the additional text (text) + + width: parent.width - 10 + wrapMode: Label.Wrap + + font.pixelSize: gradeLa.font.pixelSize + font.bold: true + + visible: text !== "" + + text: to !== "" && model.text !== "" ? to + " | " + model.text:model.to + model.text + } + } + + } + + + } + } + + Component { + id: loadingFormComp + LoadingForm {} + } + } Timer { @@ -94,7 +209,47 @@ Page { repeat: false onTriggered: { root.status = serverConn.getEvents(day) - pageLoader.newSource = "../Components/EventView.qml" + pageLoader.newSourceComponent = eventListComp } } + + Popup { + id: busyDialog + + parent: overlay + + x: Math.round((parent.width - width) / 2) + y: Math.round((parent.height - height) / 2) + + height: contentHeight * 1.5 + width: contentWidth * 1.5 + contentHeight: progressCircle.height + contentWidth: progressCircle.width + + Material.theme: root.Material.theme + + modal: true + closePolicy: "NoAutoClose" + focus: true + + ProgressCircle { + id: progressCircle + size: 50 + lineWidth: 5 + anchors.centerIn: parent + colorCircle: Material.theme === Material.Dark ? "#F48FB1":"#E91E63" + colorBackground: "transparent" + showBackground: true + arcBegin: 0 + arcEnd: 360 * serverConn.downloadProgress + animationDuration: 0 + Label { + id: progress + anchors.centerIn: parent + text: Math.round( serverConn.downloadProgress * 100 ) + "%" + } + } + } + + } diff --git a/qml/Forms/FoodPlanForm.qml b/qml/Forms/FoodPlanForm.qml index 58fb7b1..9605157 100644 --- a/qml/Forms/FoodPlanForm.qml +++ b/qml/Forms/FoodPlanForm.qml @@ -75,14 +75,6 @@ Page { } } } - - Connections { - target: pageLoader.item - onRefresh: { - pageLoader.newSource = "./LoadingForm.qml" - loadTimer.start() - } - } } Timer { diff --git a/qml/Pages/LoginPage.qml b/qml/Pages/LoginPage.qml index 9da974d..29b5e97 100644 --- a/qml/Pages/LoginPage.qml +++ b/qml/Pages/LoginPage.qml @@ -140,14 +140,17 @@ Page { rightMargin: root.width * 0.05 } - MouseArea { + ToolButton{ id: passwordHideShow anchors { top: parent.top bottom: parent.bottom right: parent.right } - width: visibleIcon.width + + icon.height: parent.height * 0.5 + icon.width: parent.height * 0.5 + icon.color: app.style.style.textColor onClicked: { if(state === "visible"){ @@ -164,13 +167,10 @@ Page { State { name: "invisible" PropertyChanges { - target: visibleIcon - scale: 0 - } - PropertyChanges { - target: invisibleIcon - scale: 1 + target: passwordHideShow + icon.name: "hide" } + PropertyChanges { target: tipasswd echoMode: TextInput.Password @@ -179,12 +179,8 @@ Page { State { name: "visible" PropertyChanges { - target: visibleIcon - scale: 1 - } - PropertyChanges { - target: invisibleIcon - scale: 0 + target: passwordHideShow + icon.name: "view" } PropertyChanges { target: tipasswd @@ -192,39 +188,8 @@ Page { } } ] - - Image { - id: visibleIcon - - anchors { - top: parent.top - bottom: parent.bottom - right: parent.right - - bottomMargin: parent.height * 0.25 - topMargin: anchors.bottomMargin - } - fillMode: Image.PreserveAspectFit - smooth: true - source: "qrc:/graphics/icons/view.png" - } - - Image { - id: invisibleIcon - - anchors { - top: parent.top - bottom: parent.bottom - right: parent.right - - bottomMargin: parent.height * 0.25 - topMargin: anchors.bottomMargin - } - fillMode: Image.PreserveAspectFit - smooth: true - source: "qrc:/graphics/icons/hide.png" - } } + } CheckDelegate { diff --git a/qml/Pages/MainPage.qml b/qml/Pages/MainPage.qml index c43e099..1d6530e 100644 --- a/qml/Pages/MainPage.qml +++ b/qml/Pages/MainPage.qml @@ -16,8 +16,8 @@ along with this program. If not, see . */ -import QtQuick 2.2 -import QtQuick.Controls 2.1 +import QtQuick 2.9 +import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 import QtQuick.Layouts 1.3 @@ -28,7 +28,7 @@ Page { id: root objectName: "MainPage" - /*Shortcut { + Shortcut { sequences: ["Esc", "Back"] enabled: formStack.depth > 1 onActivated: { @@ -36,7 +36,7 @@ Page { formStack.pop() } } - }*/ + } Rectangle { anchors.fill: parent @@ -149,6 +149,10 @@ Page { ToolButton { id: toolButton + enabled: !formStack.currentItem.locked + + opacity: enabled ? 1:0.5 + icon.name: "back" icon.color: app.style.style.textColor @@ -182,6 +186,24 @@ Page { } } + ToolButton { + id: pdfToolButton + + enabled: !formStack.currentItem.locked + + opacity: enabled ? 1:0.5 + + visible: formStack.currentItem.title === "Vertretungsplan" + + icon.name: "pdf" + icon.color: app.style.style.textColor + + onClicked: { + if(formStack.currentItem.pdfAction !== undefined) { + formStack.currentItem.pdfAction() + } + } + } } Behavior on anchors.topMargin { @@ -211,3 +233,5 @@ Page { ] } } + + diff --git a/qml/main.qml b/qml/main.qml index 2af5e7e..2776072 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -115,31 +115,34 @@ ApplicationWindow { var infoLevel // 0 - ok // 1 - info - // 2 - error + // 2 - warn + // 3 - error var errorString var errorDescription + var errorButtonOption = "" switch(errorCode) { case 0: - infoLevel = 2 + infoLevel = 3 errorString = "Keine Verbindung zum Server" errorDescription = "Bitte überprüfe deine Internetverbindung und versuche es erneut." break case 401: - infoLevel = 2 + infoLevel = 3 errorString = "Ungültige Zugangsdaten" errorDescription = "Der Server hat den Zugang verweigert, bitte überprüfe deine Zugangsdaten und versuche es erneut" break case 500: - infoLevel = 2 + infoLevel = 3 errorString = "Interner Server Fehler" errorDescription = "Scheinbar kann der Server die Anfrage im Moment nicht verarbeiten, bitte versuche es später erneut." break case 900: infoLevel = 2 - errorString = "Interner Verarbeitungsfehler" + errorString = "Verarbeitungsfehler" errorDescription = "Die Daten, die vom Server übertragen wurden, konnten nicht richtig verarbeitet werden, bitte versuche es später erneut." + errorButtonOption = "Als Pdf ansehen" break case 901: infoLevel = 1 @@ -157,17 +160,23 @@ ApplicationWindow { errorDescription = "Die aufgerufene Funktion ist momentan nicht verfügbar, bitte versuche es später erneut." break case 904: - infoLevel = 2 + infoLevel = 3 errorString = "Inkompatible API" errorDescription = "Die Version der API auf dem Server ist zu neu und kann daher nicht verarbeitet werden. Bitte aktualisiere die App auf die aktuellste Version." + errorButtonOption = "Als Pdf ansehen" + break + case 905: + infoLevel = 3 + errorString = "Interner Speicherfehler" + errorDescription = "Die Pdf-Datei konnte nicht gespeichert werden." break default: - infoLevel = 2 + infoLevel = 3 errorString = "Unerwarteter Fehler ("+errorCode+")" errorDescription = "Unbekannter Fehler bei der Verbindung mit dem Server." } - return([infoLevel, errorString, errorDescription]) + return([infoLevel, errorString, errorDescription, errorButtonOption]) } function landscape(){ diff --git a/qml/qml.qrc b/qml/qml.qrc index d57283f..90268f7 100644 --- a/qml/qml.qrc +++ b/qml/qml.qrc @@ -16,5 +16,7 @@ Forms/FilterForm.qml Components/SettingsDelegate.qml Components/FannyDataListView.qml + Components/ProgressCircle.qml + Components/PullRefresher.qml diff --git a/shared/icons/delete.png b/shared/icons/delete.png deleted file mode 100644 index 64cc7e2..0000000 Binary files a/shared/icons/delete.png and /dev/null differ diff --git a/shared/icons/ibmaterial/20x20/hide.png b/shared/icons/ibmaterial/20x20/hide.png new file mode 100644 index 0000000..40f8ea3 Binary files /dev/null and b/shared/icons/ibmaterial/20x20/hide.png differ diff --git a/shared/icons/ibmaterial/20x20/pdf.png b/shared/icons/ibmaterial/20x20/pdf.png new file mode 100644 index 0000000..58f27dd Binary files /dev/null and b/shared/icons/ibmaterial/20x20/pdf.png differ diff --git a/shared/icons/ibmaterial/20x20/view.png b/shared/icons/ibmaterial/20x20/view.png new file mode 100644 index 0000000..49b4675 Binary files /dev/null and b/shared/icons/ibmaterial/20x20/view.png differ diff --git a/shared/icons/ibmaterial/20x20@2/hide.png b/shared/icons/ibmaterial/20x20@2/hide.png new file mode 100644 index 0000000..bddb3ff Binary files /dev/null and b/shared/icons/ibmaterial/20x20@2/hide.png differ diff --git a/shared/icons/ibmaterial/20x20@2/pdf.png b/shared/icons/ibmaterial/20x20@2/pdf.png new file mode 100644 index 0000000..41413d7 Binary files /dev/null and b/shared/icons/ibmaterial/20x20@2/pdf.png differ diff --git a/shared/icons/ibmaterial/20x20@2/view.png b/shared/icons/ibmaterial/20x20@2/view.png new file mode 100644 index 0000000..1b56756 Binary files /dev/null and b/shared/icons/ibmaterial/20x20@2/view.png differ diff --git a/shared/icons/ibmaterial/20x20@3/hide.png b/shared/icons/ibmaterial/20x20@3/hide.png new file mode 100644 index 0000000..30d44e2 Binary files /dev/null and b/shared/icons/ibmaterial/20x20@3/hide.png differ diff --git a/shared/icons/ibmaterial/20x20@3/pdf.png b/shared/icons/ibmaterial/20x20@3/pdf.png new file mode 100644 index 0000000..294e3ee Binary files /dev/null and b/shared/icons/ibmaterial/20x20@3/pdf.png differ diff --git a/shared/icons/ibmaterial/20x20@3/view.png b/shared/icons/ibmaterial/20x20@3/view.png new file mode 100644 index 0000000..359fbe9 Binary files /dev/null and b/shared/icons/ibmaterial/20x20@3/view.png differ diff --git a/shared/icons/ibmaterial/20x20@4/hide.png b/shared/icons/ibmaterial/20x20@4/hide.png new file mode 100644 index 0000000..6cbf744 Binary files /dev/null and b/shared/icons/ibmaterial/20x20@4/hide.png differ diff --git a/shared/icons/ibmaterial/20x20@4/pdf.png b/shared/icons/ibmaterial/20x20@4/pdf.png new file mode 100644 index 0000000..1451375 Binary files /dev/null and b/shared/icons/ibmaterial/20x20@4/pdf.png differ diff --git a/shared/icons/ibmaterial/20x20@4/view.png b/shared/icons/ibmaterial/20x20@4/view.png new file mode 100644 index 0000000..cd9114b Binary files /dev/null and b/shared/icons/ibmaterial/20x20@4/view.png differ diff --git a/shared/shared.qrc b/shared/shared.qrc index a095c59..c427fe2 100644 --- a/shared/shared.qrc +++ b/shared/shared.qrc @@ -72,7 +72,6 @@ icons/ibmaterial/index.theme icons/back.png icons/backDark.png - icons/delete.png icons/hide.png icons/logoutBlack.png icons/logoutRed.png @@ -86,5 +85,18 @@ icons/ibmaterial/20x20@2/delete.png icons/ibmaterial/20x20@3/delete.png icons/ibmaterial/20x20@4/delete.png + icons/ibmaterial/20x20/pdf.png + icons/ibmaterial/20x20@2/pdf.png + icons/ibmaterial/20x20@3/pdf.png + icons/ibmaterial/20x20@4/pdf.png + graphics/Splashscreen.png + icons/ibmaterial/20x20/hide.png + icons/ibmaterial/20x20/view.png + icons/ibmaterial/20x20@2/hide.png + icons/ibmaterial/20x20@2/view.png + icons/ibmaterial/20x20@3/hide.png + icons/ibmaterial/20x20@3/view.png + icons/ibmaterial/20x20@4/hide.png + icons/ibmaterial/20x20@4/view.png diff --git a/sources/filehelper.cpp b/sources/filehelper.cpp new file mode 100644 index 0000000..59e583b --- /dev/null +++ b/sources/filehelper.cpp @@ -0,0 +1,110 @@ +#include "../headers/filehelper.h" + +#if defined(Q_OS_IOS) +#include +#include +#include + +#include +#include +#endif + +FileHelper::FileHelper(QObject *parent) : QObject(parent) +{ +#if defined(Q_OS_IOS) +#elif defined(Q_OS_ANDROID) + mInstance = this; +#else +#endif +} + +void FileHelper::viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) +{ +#if defined(Q_OS_IOS) +#elif defined(Q_OS_ANDROID) + 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); + } +#else + Q_UNUSED(title) + Q_UNUSED(mimeType) + Q_UNUSED(requestId) + + QDesktopServices::openUrl(QUrl::fromLocalFile(filePath)); +#endif + +} + +#if defined(Q_OS_ANDROID) +const static int RESULT_OK = -1; +const static int RESULT_CANCELED = 0; + +FileHelper* FileHelper::mInstance = nullptr; + +FileHelper* FileHelper::getInstance() +{ + if (!mInstance) { + mInstance = new FileHelper; + qWarning() << "AndroidShareUtils should be instantiated !"; + } + + return mInstance; +} + + +// used from QAndroidActivityResultReceiver +void FileHelper::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 FileHelper::onActivityResult(int requestCode, int resultCode) +{ + qDebug() << "From Java Activity onActivityResult: " << requestCode << "ResultCode:" << resultCode; + processActivityResult(requestCode, resultCode); +} + +void FileHelper::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) { + emit shareFinished(requestCode); + } else { + qDebug() << "wrong result code: " << resultCode << " from request: " << requestCode; + emit shareError(requestCode, tr("Share: an Error occured")); + } +} + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +JNIEXPORT void JNICALL + Java_de_itsblue_fannyapp_MainActivity_fireActivityResult(JNIEnv *env, + jobject obj, + jint requestCode, + jint resultCode) +{ + Q_UNUSED (obj) + Q_UNUSED (env) + FileHelper::getInstance()->onActivityResult(requestCode, resultCode); + return; +} + +#ifdef __cplusplus +} +#endif // __cplusplus +#endif //defined(Q_OS_ANDROID) diff --git a/sources/serverconn.cpp b/sources/serverconn.cpp index 251bdc3..133f6bf 100644 --- a/sources/serverconn.cpp +++ b/sources/serverconn.cpp @@ -26,6 +26,27 @@ ServerConn::ServerConn(QObject *parent) : QObject(parent) qDebug("+----- ServerConn constructor -----+"); pGlobalServConn = this; + this->fileHelper = new FileHelper(); + connect(this->fileHelper, &FileHelper::shareError, [=](){qWarning() << "share error";}); + connect(this->fileHelper, &FileHelper::shareFinished, [=](){qWarning() << "share finished";}); + connect(this->fileHelper, &FileHelper::shareNoAppAvailable, [=](){qWarning() << "share no app available";}); + + // get local work path +#if defined (Q_OS_IOS) + QString docLocationRoot = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).value(0); + qDebug() << "iOS: QStandardPaths::DocumentsLocation: " << docLocationRoot; +#elif defined(Q_OS_ANDROID) + QString docLocationRoot = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).value(0); +#else + QString docLocationRoot = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).value(0); +#endif + mDocumentsWorkPath = docLocationRoot.append("/tmp_pdf_files"); + if (!QDir(mDocumentsWorkPath).exists()) { + if (!QDir("").mkpath(mDocumentsWorkPath)) { + pGlobalAppSettings->writeSetting("localDocPathError", "true"); + } + } + // check login state int perm = pGlobalAppSettings->loadSetting("permanent").toInt(); qDebug() << "+----- login state: " << perm << " -----+"; @@ -51,9 +72,9 @@ int ServerConn::login(QString username, QString password, bool permanent) pdata.addQueryItem("password", password); // send the request - ReturnData_t ret = this->senddata(QUrl("http://www.fanny-leicht.de/j34/templates/g5_helium/intern/events.php"), pdata); + QVariantMap ret = this->senddata(QUrl("http://www.fanny-leicht.de/j34/templates/g5_helium/intern/events.php"), pdata); - if(ret.status_code == 200){ + if(ret["status"].toInt() == 200){ // if not 200 was returned -> user data was correct // store username and password in the class variables this->username = username; @@ -81,7 +102,7 @@ int ServerConn::login(QString username, QString password, bool permanent) pGlobalAppSettings->writeSetting("permanent", "0"); pGlobalAppSettings->writeSetting("username", ""); pGlobalAppSettings->writeSetting("password", ""); - return(ret.status_code); + return(ret["status"].toInt()); } } @@ -119,19 +140,19 @@ int ServerConn::getEvents(QString day) pdata.addQueryItem("day", day); // send the request - ReturnData_t ret = this->senddata(QUrl("https://www.fanny-leicht.de/j34/templates/g5_helium/intern/events.php"), pdata); + QVariantMap ret = this->senddata(QUrl("https://www.fanny-leicht.de/j34/templates/g5_helium/intern/events.php"), pdata); - if(ret.status_code != 200){ + if(ret["status"].toInt() != 200){ // if the request didn't result in a success, clear the old events, as they are probaply incorrect and return the error code this->m_events.clear(); - if(ret.status_code == 401){ + if(ret["status"].toInt() == 401){ // if the stats code is 401 -> userdata is incorrect qDebug() << "+----- checkconn: user data is incorrect -----+"; logout(); } - return ret.status_code; + return ret["status"].toInt(); } @@ -152,7 +173,7 @@ int ServerConn::getEvents(QString day) QStringList tmpEventHeader; //qDebug() << jsonString; - QJsonDocument jsonFilters = QJsonDocument::fromJson(ret.text.toUtf8()); + QJsonDocument jsonFilters = QJsonDocument::fromJson(ret["text"].toString().toUtf8()); // array with tghe whole response in it QJsonObject JsonArray = jsonFilters.object(); @@ -230,15 +251,68 @@ int ServerConn::getEvents(QString day) } // check if there is any valid data - if(tmpEvents.length() < 3){ + if(tmpEvents.length() == 0 || tmpEvents.length() == 1) { + // no data was delivered at all -> the server encountered a parse error + tmpEvents.clear(); + ret["status"].setValue(900); + } + else if(tmpEvents.length() == 2) { // remove the last (in this case the second) element, as it is unnecessary (it is the legend -> not needed when there is no data) tmpEvents.takeLast(); // set return code to 'no data' (901) - ret.status_code = 901; + ret["status"].setValue(901); } this->m_events = tmpEvents; - return(ret.status_code); + return(ret["status"].toInt()); +} + +int ServerConn::openEventPdf(QString day) { + // day: 0-today; 1-tomorrow + if(this->state != "loggedIn"){ + return 401; + } + + if(pGlobalAppSettings->loadSetting("localDocPathError") == "true") { + // we have no local document path to work with -> this is not going to work! + return 905; + } + + // add the data to the request + QUrlQuery pdata; + pdata.addQueryItem("username", this->username); + pdata.addQueryItem("password", this->password); + pdata.addQueryItem("mode", pGlobalAppSettings->loadSetting("teacherMode") == "true" ? "1":"0"); + pdata.addQueryItem("day", day); + pdata.addQueryItem("asPdf", "true"); + + // send the request + QVariantMap ret = this->senddata(QUrl("https://www.fanny-leicht.de/j34/templates/g5_helium/intern/events.php"), pdata, true); + + if(ret["status"].toInt() != 200){ + // if the request didn't result in a success, clear the old events, as they are probaply incorrect and return the error code + this->m_events.clear(); + + if(ret["status"].toInt() == 401){ + // if the stats code is 401 -> userdata is incorrect + qDebug() << "+----- checkconn: user data is incorrect -----+"; + logout(); + } + + return ret["status"].toInt(); + } + + QString path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + QString filname = (QString(pGlobalAppSettings->loadSetting("teacherMode") == "true" ? "l":"s") + QString(day == "0" ? "heute":"morgen" )); + QFile file(mDocumentsWorkPath + "/" + filname + ".pdf"); + file.remove(); + file.open(QIODevice::ReadWrite); + file.write(ret["data"].toByteArray()); + file.close(); + + this->fileHelper->viewFile(mDocumentsWorkPath + "/" + filname + ".pdf", "SMorgen", "application/pdf", 1); + + return 200; } int ServerConn::getFoodPlan() @@ -255,18 +329,18 @@ int ServerConn::getFoodPlan() QUrlQuery pdata; // send the request to the server - ReturnData_t ret = this->senddata(QUrl(url), pdata); + QVariantMap ret = this->senddata(QUrl(url), pdata); - if(ret.status_code != 200){ + if(ret["status"].toInt() != 200){ // if the request didn't result in a success, return the error code // if the request failed but there is still old data available if(!this->m_weekplan.isEmpty()){ // set the status code to 902 (old data) - ret.status_code = 902; + ret["status"].setValue(902); } - return(ret.status_code); + return(ret["status"].toInt()); } // list to be returned @@ -274,8 +348,8 @@ int ServerConn::getFoodPlan() QList tmpWeekplan; //qDebug() << jsonString; - QJsonDocument jsonDoc = QJsonDocument::fromJson(ret.text.toUtf8()); - //qDebug() << ret.text; + QJsonDocument jsonDoc = QJsonDocument::fromJson(ret["text"].toString().toUtf8()); + //qDebug() << ret["text"].toString(); // array with the whole response in it QJsonArray foodplanDays = jsonDoc.array(); @@ -329,12 +403,12 @@ int ServerConn::getFoodPlan() return(200); } -ReturnData_t ServerConn::senddata(QUrl serviceUrl, QUrlQuery pdata) +QVariantMap ServerConn::senddata(QUrl serviceUrl, QUrlQuery pdata, bool raw) { // create network manager QNetworkAccessManager * networkManager = new QNetworkAccessManager(); - ReturnData_t ret; //this is a custom type to store the return-data + QVariantMap ret; //this is a custom type to store the return-data // Create network request QNetworkRequest request(serviceUrl); @@ -362,22 +436,33 @@ ReturnData_t ServerConn::senddata(QUrl serviceUrl, QUrlQuery pdata) // start the timer timer.start(10000); - qDebug() << "+--- starting request now..."; + this->updateDownloadProgress(0, 1); reply = networkManager->post(request, pdata.toString(QUrl::FullyEncoded).toUtf8()); connect(reply, &QNetworkReply::sslErrors, this, [=](){ reply->ignoreSslErrors(); }); + connect(reply, SIGNAL(downloadProgress(qint64, qint64)), + this, SLOT(updateDownloadProgress(qint64, qint64))); + // start the loop loop.exec(); - qDebug() << "+--- request finished"; + if(!timer.isActive()) { + // timeout + return {{"status", 0}}; + } //get the status code QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - ret.status_code = status_code.toInt(); + ret.insert("status", status_code.toInt()); //get the full text response - ret.text = QString::fromUtf8(reply->readAll()); + if(!raw) { + ret.insert("text", QString::fromUtf8(reply->readAll())); + } + else { + ret.insert("data", reply->readAll()); + } // delete the reply object reply->deleteLater(); @@ -389,6 +474,22 @@ ReturnData_t ServerConn::senddata(QUrl serviceUrl, QUrlQuery pdata) return(ret); } +void ServerConn::updateDownloadProgress(qint64 read, qint64 total) +{ + double progress; + + if(total <= 0) + progress = 0; + else + progress = (double(read) / double(total)); + + if(progress < 0) + progress = 0; + + this->downloadProgress = progress; + emit this->downloadProgressChanged(); +} + QString ServerConn::getState() { return(this->state); } @@ -402,6 +503,10 @@ void ServerConn::setState(QString state) { } } +double ServerConn::getDownloadProgress() { + return this->downloadProgress; +} + ServerConn::~ServerConn() { qDebug("+----- ServerConn destruktor -----+");