- release candidate 1.1.0
- added the ability to download pdf files - added warning message "Processing error" - new pull-to-refresh indicator - fixed eye-ion on login screen
12
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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<manifest package="com.itsblue.flgvertretungtest" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0.2" android:versionCode="16" android:installLocation="auto">
|
||||
<manifest package="com.itsblue.flgvertretungbeta" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.1.0" android:versionCode="17" android:installLocation="auto">
|
||||
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="28"/>
|
||||
|
||||
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
|
||||
|
@ -13,7 +13,7 @@
|
|||
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
||||
|
||||
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="fannyapp" android:icon="@drawable/icon">
|
||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="fannyapp" android:screenOrientation="unspecified" android:launchMode="singleTop">
|
||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="de.itsblue.fannyapp.MainActivity" android:label="fannyapp" android:screenOrientation="unspecified" android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
@ -52,8 +52,8 @@
|
|||
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
|
||||
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
|
||||
are done populating your window with content. -->
|
||||
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/screen"/>
|
||||
<!--meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" />
|
||||
<!--meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/screen"/>
|
||||
<meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" />
|
||||
<meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" />
|
||||
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/>
|
||||
<meta-data android:name="android.app.splash_screen_sticky" android:value="true"/-->
|
||||
|
@ -83,6 +83,10 @@
|
|||
</activity>
|
||||
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
<!-- Register the File Provider for document sharing -->
|
||||
<provider android:name="android.support.v4.content.FileProvider" android:authorities="de.itsblue.fannyapp.fileprovider" android:grantUriPermissions="true" android:exported="false">
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/>
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
|
|
58
android-sources/build.gradle
Normal file
|
@ -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
|
||||
}
|
||||
}
|
BIN
android-sources/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
5
android-sources/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -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
|
172
android-sources/gradlew
vendored
Executable file
|
@ -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" "$@"
|
84
android-sources/gradlew.bat
vendored
Normal file
|
@ -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
|
BIN
android-sources/libs/arm64-v8a/lib/libcrypto.a
Normal file
BIN
android-sources/libs/arm64-v8a/lib/libcrypto.so.1.1
Executable file
BIN
android-sources/libs/arm64-v8a/lib/libssl.a
Normal file
BIN
android-sources/libs/arm64-v8a/lib/libssl.so.1.1
Executable file
3
android-sources/libs/arm64-v8a/lib/symlink-windows.bat
Normal file
|
@ -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
|
BIN
android-sources/libs/armeabi/lib/libcrypto.a
Normal file
BIN
android-sources/libs/armeabi/lib/libcrypto.so.1.1
Executable file
BIN
android-sources/libs/armeabi/lib/libssl.a
Normal file
BIN
android-sources/libs/armeabi/lib/libssl.so.1.1
Executable file
3
android-sources/libs/armeabi/lib/symlink-windows.bat
Normal file
|
@ -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
|
BIN
android-sources/libs/x86/lib/libcrypto.a
Normal file
BIN
android-sources/libs/x86/lib/libcrypto.so.1.1
Executable file
BIN
android-sources/libs/x86/lib/libssl.a
Normal file
BIN
android-sources/libs/x86/lib/libssl.so.1.1
Executable file
3
android-sources/libs/x86/lib/symlink-windows.bat
Normal file
|
@ -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
|
BIN
android-sources/libs/x86_64/lib/libcrypto.a
Normal file
BIN
android-sources/libs/x86_64/lib/libcrypto.so.1.1
Executable file
BIN
android-sources/libs/x86_64/lib/libssl.a
Normal file
BIN
android-sources/libs/x86_64/lib/libssl.so.1.1
Executable file
3
android-sources/libs/x86_64/lib/symlink-windows.bat
Normal file
|
@ -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
|
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 5.5 KiB |
25
android-sources/res/values/libs.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<array name="qt_sources">
|
||||
<item>https://download.qt.io/ministro/android/qt5/qt-5.9</item>
|
||||
</array>
|
||||
|
||||
<!-- The following is handled automatically by the deployment tool. It should
|
||||
not be edited manually. -->
|
||||
|
||||
<array name="bundled_libs">
|
||||
<!-- %%INSERT_EXTRA_LIBS%% -->
|
||||
</array>
|
||||
|
||||
<array name="qt_libs">
|
||||
<!-- %%INSERT_QT_LIBS%% -->
|
||||
</array>
|
||||
|
||||
<array name="bundled_in_lib">
|
||||
<!-- %%INSERT_BUNDLED_IN_LIB%% -->
|
||||
</array>
|
||||
<array name="bundled_in_assets">
|
||||
<!-- %%INSERT_BUNDLED_IN_ASSETS%% -->
|
||||
</array>
|
||||
|
||||
</resources>
|
3
android-sources/res/xml/filepaths.xml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<files-path name="tmp_pdf_files" path="tmp_pdf_files/" />
|
||||
</paths>
|
39
android-sources/src/de/itsblue/fannyapp/MainActivity.java
Normal file
|
@ -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
|
||||
|
199
android-sources/src/org/ekkescorner/utils/QShareUtils.java
Executable file
|
@ -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<ResolveInfo> 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<ResolveInfo>() {
|
||||
@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<Intent> targetedIntents = new ArrayList<Intent>();
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
50
headers/filehelper.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
#ifndef FILEHELPER_H
|
||||
#define FILEHELPER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
mPlatformShareUtils = new IosShareUtils(this);
|
||||
#elif defined(Q_OS_ANDROID)
|
||||
#include <QtAndroid>
|
||||
#include <QAndroidActivityResultReceiver>
|
||||
#else
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
#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
|
|
@ -29,21 +29,17 @@
|
|||
#include <QDesktopServices>
|
||||
|
||||
#include "headers/appsettings.h"
|
||||
#include "headers/filehelper.h"
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include <QtAndroidExtras>
|
||||
#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<int> 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<QStringList> m_weekplan;
|
||||
|
|
|
@ -29,7 +29,6 @@ FannyDataListView {
|
|||
id: foodPlanModel
|
||||
}
|
||||
|
||||
|
||||
delegate: Button {
|
||||
id: delegate
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
90
qml/Components/ProgressCircle.qml
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
327
qml/Components/PullRefresher.qml
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
}
|
|
@ -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 ) + "%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -75,14 +75,6 @@ Page {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: pageLoader.item
|
||||
onRefresh: {
|
||||
pageLoader.newSource = "./LoadingForm.qml"
|
||||
loadTimer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 {
|
|||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
25
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(){
|
||||
|
|
|
@ -16,5 +16,7 @@
|
|||
<file>Forms/FilterForm.qml</file>
|
||||
<file>Components/SettingsDelegate.qml</file>
|
||||
<file>Components/FannyDataListView.qml</file>
|
||||
<file>Components/ProgressCircle.qml</file>
|
||||
<file>Components/PullRefresher.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
Before Width: | Height: | Size: 776 B |
BIN
shared/icons/ibmaterial/20x20/hide.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
shared/icons/ibmaterial/20x20/pdf.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
shared/icons/ibmaterial/20x20/view.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
shared/icons/ibmaterial/20x20@2/hide.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
shared/icons/ibmaterial/20x20@2/pdf.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
shared/icons/ibmaterial/20x20@2/view.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
shared/icons/ibmaterial/20x20@3/hide.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
shared/icons/ibmaterial/20x20@3/pdf.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
shared/icons/ibmaterial/20x20@3/view.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
shared/icons/ibmaterial/20x20@4/hide.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
shared/icons/ibmaterial/20x20@4/pdf.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
shared/icons/ibmaterial/20x20@4/view.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
|
@ -72,7 +72,6 @@
|
|||
<file>icons/ibmaterial/index.theme</file>
|
||||
<file>icons/back.png</file>
|
||||
<file>icons/backDark.png</file>
|
||||
<file>icons/delete.png</file>
|
||||
<file>icons/hide.png</file>
|
||||
<file>icons/logoutBlack.png</file>
|
||||
<file>icons/logoutRed.png</file>
|
||||
|
@ -86,5 +85,18 @@
|
|||
<file>icons/ibmaterial/20x20@2/delete.png</file>
|
||||
<file>icons/ibmaterial/20x20@3/delete.png</file>
|
||||
<file>icons/ibmaterial/20x20@4/delete.png</file>
|
||||
<file>icons/ibmaterial/20x20/pdf.png</file>
|
||||
<file>icons/ibmaterial/20x20@2/pdf.png</file>
|
||||
<file>icons/ibmaterial/20x20@3/pdf.png</file>
|
||||
<file>icons/ibmaterial/20x20@4/pdf.png</file>
|
||||
<file>graphics/Splashscreen.png</file>
|
||||
<file>icons/ibmaterial/20x20/hide.png</file>
|
||||
<file>icons/ibmaterial/20x20/view.png</file>
|
||||
<file>icons/ibmaterial/20x20@2/hide.png</file>
|
||||
<file>icons/ibmaterial/20x20@2/view.png</file>
|
||||
<file>icons/ibmaterial/20x20@3/hide.png</file>
|
||||
<file>icons/ibmaterial/20x20@3/view.png</file>
|
||||
<file>icons/ibmaterial/20x20@4/hide.png</file>
|
||||
<file>icons/ibmaterial/20x20@4/view.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
110
sources/filehelper.cpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
#include "../headers/filehelper.h"
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
#include <QUrl>
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
|
||||
#include <QtAndroidExtras/QAndroidJniObject>
|
||||
#include <jni.h>
|
||||
#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<jboolean>("org/ekkescorner/utils/QShareUtils",
|
||||
"viewFile",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
|
||||
jsPath.object<jstring>(), jsTitle.object<jstring>(), jsMimeType.object<jstring>(), 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)
|
|
@ -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<QStringList> 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 -----+");
|
||||
|
|