Compare commits

..

13 commits

Author SHA1 Message Date
bef407ebc3
Fix: Add referer header 2022-10-24 10:07:39 +02:00
d2a0c9a2d5
up 2021-05-22 18:00:09 +02:00
2ffbd09afc
Close #14 2021-03-07 20:42:03 +01:00
64670b9d88
Fix #14 2021-03-07 20:39:07 +01:00
85c8760bed
- moved some stuff
- results work now (still basic)
2020-11-15 14:48:12 +01:00
9d8e1c7c42
- now parsing discipline and status of category 2020-11-08 10:47:44 +01:00
035a2edcdf
- some fixes for calendar: Leagues are now handlesd properly
- some fixes for results
2020-11-05 19:37:13 +01:00
252f8dbfdd
results work now for DR as well. 2020-11-04 17:25:28 +01:00
9b762ea246
many new implementations:
- Results are now starting to work on VL
2020-11-04 13:55:37 +01:00
c44380d1bf
Many more implementations!
Calendar works now for VL and DR API
2020-11-03 15:56:43 +01:00
0c7f5a42e4
remove som unnecessary stuff 2020-10-31 15:16:53 +01:00
77070aa98b
started to implement new structure 2020-10-31 15:16:06 +01:00
92caac3309
started to implement new structure 2020-10-31 15:16:00 +01:00
234 changed files with 6674 additions and 6548 deletions

View file

@ -1,30 +0,0 @@
kind: pipeline
name: default
steps:
- name: submodules
image: alpine/git
commands:
- git submodule update --init --recursive
- name: build app
image: itsblue.dev/plugins/qt-android:5.15.5-4
settings:
qmake_arguments: "CONFIG+=release"
androiddeployqt_arguments: "--android-platform android-31 --aab"
android_keystore_data:
from_secret: android-release-keystore
android_keystore_alias: "bmca"
android_keystore_store_pass:
from_secret: android-release-keystore-password
- name: release
image: plugins/gitea-release
settings:
base_url: https://itsblue.dev
api_key:
from_secret: gitea-token
files:
- outputs/apk/release/android-build-release-signed.apk
- outputs/bundle/release/android-build-release.aab
when:
event: tag

3
.gitignore vendored
View file

@ -1,5 +1,2 @@
*.pro.user*
*.DS_Store
.bundle
vendor
outputs

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "qzxing"]
path = qzxing
url = https://github.com/ftylitak/qzxing.git

View file

@ -3,75 +3,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
# [0.8.0] - 2023-06-05
### Added
- Show both results in speed quali
# [0.7.3] - 2023-05-02
### Removed
- Im-App purchase on android
# [0.7.1] - 2022-08-22
### Fixed
- remove unused ACCESS_FINE_LOCATION permission
- missing openssl libraries which lead to an error in the https connection
# [0.7.0] - 2022-08-14
### Added
- Startnumbers in speed flowchart
### Fixed
- Sharing the poster on Android
- Rendering the speed flowchart when starting at 1/2-Final
### Changed
- The qualification rank is now smaller and grayed out in the speed flowchart
### Removed
- URL handler for app.bluerock.dev, it is not needed
# [0.6.1] - 2021-10-20
### Added
- Privacy policy link to comply with google play policies
# [0.6.0] - 2021-08-07
### Changed
- The subtitle in results and startlists is now the route name instead of the category name
### Added
- Dark mode
- QR-Code scanning
- Sharing of every view using either link, QR-Code or a poster
- Text which is too large too fit is scrollable now in most places
- German translations
- URL handler for https://l.bluerock.dev and https://app.bluerock.dev
### Fixed
- Rare issue with missing background in boulder result rect
# [0.5.1] - 2021-07-06
### Fixed
- In-app purchase
# [0.05] - 2021-06-07
# [0.03.1] - UR
### Changed
- the boulder result rect doesn't have a background if there is no result now
- the selected route is kept when changing cats
- the boulder result rect now shows the number of tries when there is not zone or top yet
- the speed flowchart now blocks the back key from closing the competition and instead closes itself
- redesigned start page
- added disclaimer regarding IFSC results
- some internal refactoring
- the calendar now scrolls less far down
- improoved layout in landscape mode
- some design changes in profile page and speed flowchart
### Added
- Second link for "further infos" in calendar
# [0.03.0] - 2019-07-11
### Added
- it is now possible to pin competitions and only show these
- it is not possible to pin competitions and only show these
- flowcharts for the speed finals are now available
# [0.02.0] - 2019-06-23
@ -81,7 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- event website url is now present in the cat select dialog if available
### Changed
- competitons are clickable if there is a infosheet or event website url available even when they don't have any categories
- competitons are clickable if there is a infosheet or event website url available event when they don't have any categories
# [0.01.6] - 2019-06-15
### Fixed

View file

@ -1,3 +0,0 @@
source "https://rubygems.org"
gem "fastlane"

View file

@ -1,218 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.5)
rexml
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.612.0)
aws-sdk-core (3.131.5)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.58.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.114.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.5.1)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.92.4)
faraday (1.10.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.208.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
naturally (~> 2.2)
optparse (~> 0.1.1)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.25.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-core (0.7.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.13.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-playcustomapp_v1 (0.10.0)
google-apis-core (>= 0.7, < 2.a)
google-apis-storage_v1 (0.18.0)
google-apis-core (>= 0.7, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.2.0)
google-cloud-storage (1.37.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.2.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.1)
json (2.6.2)
jwt (2.4.1)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.1.2)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.1)
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
public_suffix (4.0.7)
rake (13.0.6)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.5)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.17.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
x86_64-linux
DEPENDENCIES
fastlane
BUNDLED WITH
2.3.19

40
NewStructure.md Normal file
View file

@ -0,0 +1,40 @@
# Layer 1:
- Server Conector -> gets data as QVariant
- One class: BRServerConnector
- URL has to be given as string
# Layer 2:
- Translation layer
- classes:
- BRController
- BRProvider
- BRProvderDr
# Layer 3
- Consisten data layer
- classes:
- BRWidget -> gets a provider, has an int load() function
- BRCalendar
- BRCompetition
- BRCategory
- BRResult
- BRBoulderResult
- BRLeadResult
- BRSpeedResult
# layer 4
- QML
- Will call functions from Layer 2 and get Objects from Layer 3
# Season and Leagues handling
1. Calendar loads
-> Calendar gets available seasons (which get available leagues)
-> Data loader decides on default season and default league(s)
-> Data loader loads default competitions
2. Calendar displays competitions
3. Current season is changed:
-> calendar reloads
-> Data loader sees that the availableSeasons are already
4. Current League is changed:
-> calendar reloads

View file

@ -1,20 +1,3 @@
# Digital Rock ranking
App to view ranking and calendar data from https://www.digitalrock.de/
# Init
´´´
git submodule init
git submodule update
´´´
# Poster
The poster offsets are (always top left of the element):
- Width: 1654
- Height: 2339
### QR-Code
- Cooridnates: 414, 414
- Size: 1650x1650
### Comp name
- Cooridnates: x: 324, y: 2500
- Size: 64 per line; 1835 width; 150 max height
App to view ranking and calendar data from https://www.digitalrock.de/

View file

@ -0,0 +1,74 @@
<?xml version="1.0"?>
<manifest package="com.itsblue.blueROCKtest" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="0.03.1" android:versionCode="13" android:installLocation="auto">
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="blueROCK" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="blueROCK" android:screenOrientation="unspecified" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="blueROCK"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/>
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="1"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so:lib/libQt5QuickParticles.so"/>
<meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar:jar/QtAndroidBearer.jar:jar/QtAndroidExtras.jar"/>
<meta-data android:name="android.app.static_init_classes" android:value=""/>
<!-- Used to specify custom system library path to run with local system libs -->
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- 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"/ -->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
"applicationStateChanged(Qt::ApplicationSuspended)"
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="full"/>
<!-- extract android style -->
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
<uses-permission android:name="com.android.vending.BILLING"/>
</manifest>

View file

@ -1,8 +1,8 @@
<?xml version="1.0"?>
<manifest package="com.itsblue.blueROCK" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:installLocation="auto">
<manifest package="com.itsblue.blueROCKtest" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:installLocation="auto">
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS -->
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
@ -10,7 +10,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="-- %%INSERT_APP_NAME%% --" android:extractNativeLibs="true" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="de.itsblue.blueROCK.MainActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleInstance" android:exported="true">
<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="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@ -68,26 +68,9 @@
-->
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
<!-- Handle shared incoming urls -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<!-- Accepts URIs that begin with "https://l.bluerock.dev/” -->
<data android:scheme="https" android:host="l.bluerock.dev" android:pathPattern=".*"/>
</intent-filter>
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
<provider android:name="android.support.v4.content.FileProvider" android:authorities="de.itsblue.blueROCK.fileprovider" android:grantUriPermissions="true" android:exported="false">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/>
</provider>
</application>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
</manifest>

View file

@ -18,7 +18,6 @@ apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
compile 'com.android.support:support-v4:25.3.1'
}
android {
@ -52,15 +51,6 @@ android {
}
}
tasks.withType(JavaCompile) {
options.incremental = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
@ -69,15 +59,10 @@ android {
aaptOptions {
noCompress 'rcc'
}
defaultConfig {
resConfig "en"
minSdkVersion = qtMinSdkVersion
targetSdkVersion = qtTargetSdkVersion
}
lintOptions {
checkReleaseBuilds false
abortOnError false
targetSdkVersion = 29
//qtTargetSdkVersion
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View file

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View file

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View file

@ -1,11 +0,0 @@
# Project-wide Gradle settings.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m
# Gradle caching allows reusing the build artifacts from a previous
# build with the same inputs. However, over time, the cache size will
# grow. Uncomment the following line to enable it.
#org.gradle.caching=true

View file

@ -1,3 +0,0 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="temporaryFiles" path="temporaryFiles/" />
</paths>

View file

@ -1,232 +0,0 @@
// (c) 2017 Ekkehard Gentz (ekke)
// this project is based on ideas from
// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
// see github project https://github.com/lasconic/ShareUtils-QML
// also inspired by:
// https://www.androidcode.ninja/android-share-intent-example/
// https://www.calligra.org/blogs/sharing-with-qt-on-android/
// https://stackoverflow.com/questions/7156932/open-file-in-another-app
// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
// OpenURL in At Android: got ideas from:
// https://github.com/BernhardWenzel/open-url-in-qt-android
// https://github.com/tobiatesan/android_intents_qt
//
// see also /COPYRIGHT and /LICENSE
package de.itsblue.blueROCK;
import org.qtproject.qt5.android.QtNative;
import org.qtproject.qt5.android.bindings.QtActivity;
import android.os.*;
import android.content.*;
import android.app.*;
import java.lang.String;
import android.content.Intent;
import java.io.File;
import android.net.Uri;
import android.util.Log;
import android.content.ContentResolver;
import android.webkit.MimeTypeMap;
import org.ekkescorner.utils.*;
public class MainActivity extends QtActivity
{
// native - must be implemented in Cpp via JNI
// 'file' scheme or resolved from 'content' scheme:
public static native void setFileUrlReceived(String url);
//
public static native void setOtherUrlReceived(String url, String scheme);
// InputStream from 'content' scheme:
public static native void setFileReceivedAndSaved(String url);
//
public static native void fireActivityResult(int requestCode, int resultCode);
//
public static native boolean checkFileExits(String url);
public static boolean isIntentPending;
public static boolean isInitialized;
public static String workingDirPath;
// Use a custom Chooser without providing own App as share target !
// see QShareUtils.java createCustomChooserAndStartActivity()
// Selecting your own App as target could cause AndroidOS to call
// onCreate() instead of onNewIntent()
// and then you are in trouble because we're using 'singleInstance' as LaunchMode
// more details: my blog at Qt
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("ekkescorner", "onCreate QShareActivity");
// now we're checking if the App was started from another Android App via Intent
Intent theIntent = getIntent();
if (theIntent != null){
String theAction = theIntent.getAction();
if (theAction != null){
Log.d("ekkescorner onCreate ", theAction);
// QML UI not ready yet
// delay processIntent();
isIntentPending = true;
}
}
} // onCreate
// WIP - trying to find a solution to survive a 2nd onCreate
// ongoing discussion in QtMob (Slack)
// from other Apps not respecting that you only have a singleInstance
// there are problems per ex. sharing a file from Google Files App,
// but working well using Xiaomi FileManager App
@Override
public void onDestroy() {
Log.d("ekkescorner", "onDestroy QShareActivity");
// super.onDestroy();
// System.exit() closes the App before doing onCreate() again
// then the App was restarted, but looses context
// This works for Samsung My Files
// but Google Files doesn't call onDestroy()
System.exit(0);
}
// we start Activity with result code
// to test JNI with QAndroidActivityResultReceiver you must comment or rename
// this method here - otherwise you'll get wrong request or result codes
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Check which request we're responding to
Log.d("ekkescorner onActivityResult", "requestCode: "+requestCode);
if (resultCode == RESULT_OK) {
Log.d("ekkescorner onActivityResult - resultCode: ", "SUCCESS");
} else {
Log.d("ekkescorner onActivityResult - resultCode: ", "CANCEL");
}
// hint: result comes back too fast for Action SEND
// if you want to delete/move the File add a Timer w 500ms delay
// see Example App main.qml - delayDeleteTimer
// if you want to revoke permissions for older OS
// it makes sense also do this after the delay
fireActivityResult(requestCode, resultCode);
}
// if we are opened from other apps:
@Override
public void onNewIntent(Intent intent) {
Log.d("ekkescorner", "onNewIntent");
super.onNewIntent(intent);
setIntent(intent);
// Intent will be processed, if all is initialized and Qt / QML can handle the event
if(isInitialized) {
processIntent();
} else {
isIntentPending = true;
}
} // onNewIntent
public void checkPendingIntents(String workingDir) {
isInitialized = true;
workingDirPath = workingDir;
Log.d("ekkescorner", workingDirPath);
if(isIntentPending) {
isIntentPending = false;
Log.d("ekkescorner", "checkPendingIntents: true");
processIntent();
} else {
Log.d("ekkescorner", "nothingPending");
}
} // checkPendingIntents
// process the Intent if Action is SEND or VIEW
private void processIntent(){
Intent intent = getIntent();
Uri intentUri;
String intentScheme;
String intentAction;
// we are listening to android.intent.action.SEND or VIEW (see Manifest)
if (intent.getAction().equals("android.intent.action.VIEW")){
intentAction = "VIEW";
intentUri = intent.getData();
} else if (intent.getAction().equals("android.intent.action.SEND")){
intentAction = "SEND";
Bundle bundle = intent.getExtras();
intentUri = (Uri)bundle.get(Intent.EXTRA_STREAM);
} else {
Log.d("ekkescorner Intent unknown action:", intent.getAction());
return;
}
Log.d("ekkescorner action:", intentAction);
if (intentUri == null){
Log.d("ekkescorner Intent URI:", "is null");
return;
}
Log.d("ekkescorner Intent URI:", intentUri.toString());
// content or file
intentScheme = intentUri.getScheme();
if (intentScheme == null){
Log.d("ekkescorner Intent URI Scheme:", "is null");
return;
}
if(intentScheme.equals("file")){
// URI as encoded string
Log.d("ekkescorner Intent File URI: ", intentUri.toString());
setFileUrlReceived(intentUri.toString());
// we are done Qt can deal with file scheme
return;
}
if(!intentScheme.equals("content")){
Log.d("ekkescorner Intent URI unknown scheme: ", intentScheme);
setOtherUrlReceived(intentUri.toString(), intentScheme);
return;
}
// ok - it's a content scheme URI
// we will try to resolve the Path to a File URI
// if this won't work or if the File cannot be opened,
// we'll try to copy the file into our App working dir via InputStream
// hopefully in most cases PathResolver will give a path
// you need the file extension, MimeType or Name from ContentResolver ?
// here's HowTo get it:
Log.d("ekkescorner Intent Content URI: ", intentUri.toString());
ContentResolver cR = this.getContentResolver();
MimeTypeMap mime = MimeTypeMap.getSingleton();
String fileExtension = mime.getExtensionFromMimeType(cR.getType(intentUri));
Log.d("ekkescorner","Intent extension: "+fileExtension);
String mimeType = cR.getType(intentUri);
Log.d("ekkescorner"," Intent MimeType: "+mimeType);
String name = QShareUtils.getContentName(cR, intentUri);
if(name != null) {
Log.d("ekkescorner Intent Name:", name);
} else {
Log.d("ekkescorner Intent Name:", "is NULL");
}
String filePath;
filePath = QSharePathResolver.getRealPathFromURI(this, intentUri);
if(filePath == null) {
Log.d("ekkescorner QSharePathResolver:", "filePath is NULL");
} else {
Log.d("ekkescorner QSharePathResolver:", filePath);
// to be safe check if this File Url really can be opened by Qt
// there were problems with MS office apps on Android 7
if (checkFileExits(filePath)) {
setFileUrlReceived(filePath);
// we are done Qt can deal with file scheme
return;
}
}
// trying the InputStream way:
filePath = QShareUtils.createFile(cR, intentUri, workingDirPath);
if(filePath == null) {
Log.d("ekkescorner Intent FilePath:", "is NULL");
return;
}
setFileReceivedAndSaved(filePath);
} // processIntent
} // class QShareActivity

View file

@ -1,223 +0,0 @@
// from: https://github.com/wkh237/react-native-fetch-blob/blob/master/android/src/main/java/com/RNFetchBlob/Utils/PathResolver.java
// MIT License, see: https://github.com/wkh237/react-native-fetch-blob/blob/master/LICENSE
// original copyright: Copyright (c) 2017 xeiyan@gmail.com
// src slightly modified to be used into Qt Projects: (c) 2017 ekke@ekkes-corner.org
package org.ekkescorner.utils;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.content.ContentUris;
import android.os.Environment;
import android.content.ContentResolver;
import java.io.File;
import java.io.InputStream;
import java.io.FileOutputStream;
import android.util.Log;
import java.lang.NumberFormatException;
public class QSharePathResolver {
public static String getRealPathFromURI(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
Log.d("ekkescorner"," isExternalStorageDocument");
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
Log.d("ekkescorner"," isDownloadsDocument");
final String id = DocumentsContract.getDocumentId(uri);
Log.d("ekkescorner"," getDocumentId "+id);
long longId = 0;
try
{
longId = Long.valueOf(id);
}
catch(NumberFormatException nfe)
{
return getDataColumn(context, uri, null, null);
}
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), longId);
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
Log.d("ekkescorner"," isMediaDocument");
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] {
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
else if ("content".equalsIgnoreCase(uri.getScheme())) {
Log.d("ekkescorner"," is uri.getScheme()");
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// Other Providers
else{
Log.d("ekkescorner ","is Other Provider");
try {
InputStream attachment = context.getContentResolver().openInputStream(uri);
if (attachment != null) {
String filename = getContentName(context.getContentResolver(), uri);
if (filename != null) {
File file = new File(context.getCacheDir(), filename);
FileOutputStream tmp = new FileOutputStream(file);
byte[] buffer = new byte[1024];
while (attachment.read(buffer) > 0) {
tmp.write(buffer);
}
tmp.close();
attachment.close();
return file.getAbsolutePath();
}
}
} catch (Exception e) {
// TODO SIGNAL shareError()
return null;
}
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
Log.d("ekkescorner ","NOT DocumentsContract.isDocumentUri");
Log.d("ekkescorner"," is uri.getScheme()");
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
Log.d("ekkescorner"," return: getDataColumn ");
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
Log.d("ekkescorner ","NOT DocumentsContract.isDocumentUri");
Log.d("ekkescorner"," is file scheme");
return uri.getPath();
}
return null;
}
private static String getContentName(ContentResolver resolver, Uri uri) {
Cursor cursor = resolver.query(uri, null, null, null, null);
cursor.moveToFirst();
int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
if (nameIndex >= 0) {
String name = cursor.getString(nameIndex);
cursor.close();
return name;
}
cursor.close();
return null;
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
String result = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
result = cursor.getString(index);
}
}
catch (Exception ex) {
ex.printStackTrace();
return null;
}
finally {
if (cursor != null)
cursor.close();
}
return result;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
}

View file

@ -1,396 +0,0 @@
// (c) 2017 Ekkehard Gentz (ekke)
// this project is based on ideas from
// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
// see github project https://github.com/lasconic/ShareUtils-QML
// also inspired by:
// https://www.androidcode.ninja/android-share-intent-example/
// https://www.calligra.org/blogs/sharing-with-qt-on-android/
// https://stackoverflow.com/questions/7156932/open-file-in-another-app
// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
// https://stackoverflow.com/questions/5734678/custom-filtering-of-intent-chooser-based-on-installed-android-package-name
// see also /COPYRIGHT and /LICENSE
package org.ekkescorner.utils;
import org.qtproject.qt5.android.QtNative;
import java.lang.String;
import android.content.Intent;
import java.io.File;
import android.net.Uri;
import android.util.Log;
import android.content.ContentResolver;
import android.database.Cursor;
import android.provider.MediaStore;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.util.List;
import android.content.pm.ResolveInfo;
import java.util.ArrayList;
import android.content.pm.PackageManager;
import java.util.Comparator;
import java.util.Collections;
import android.content.Context;
import android.os.Parcelable;
import android.os.Build;
import android.support.v4.content.FileProvider;
import android.support.v4.app.ShareCompat;
public class QShareUtils
{
// reference Authority as defined in AndroidManifest.xml
private static String AUTHORITY="de.itsblue.blueROCK.fileprovider";
protected QShareUtils()
{
Log.d("ekkescorner", "QShareUtils()");
}
public static boolean checkMimeTypeView(String mimeType) {
if (QtNative.activity() == null)
return false;
Intent myIntent = new Intent();
myIntent.setAction(Intent.ACTION_VIEW);
// without an URI resolve always fails
// an empty URI allows to resolve the Activity
File fileToShare = new File("");
Uri uri = Uri.fromFile(fileToShare);
myIntent.setDataAndType(uri, mimeType);
// Verify that the intent will resolve to an activity
if (myIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
Log.d("ekkescorner checkMime ", "YEP - we can go on and View");
return true;
} else {
Log.d("ekkescorner checkMime", "sorry - no App available to View");
}
return false;
}
public static boolean checkMimeTypeEdit(String mimeType) {
if (QtNative.activity() == null)
return false;
Intent myIntent = new Intent();
myIntent.setAction(Intent.ACTION_EDIT);
// without an URI resolve always fails
// an empty URI allows to resolve the Activity
File fileToShare = new File("");
Uri uri = Uri.fromFile(fileToShare);
myIntent.setDataAndType(uri, mimeType);
// Verify that the intent will resolve to an activity
if (myIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
Log.d("ekkescorner checkMime ", "YEP - we can go on and Edit");
return true;
} else {
Log.d("ekkescorner checkMime", "sorry - no App available to Edit");
}
return false;
}
public static boolean shareText(String text) {
if (QtNative.activity() == null)
return false;
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, text);
sendIntent.setType("text/plain");
// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
QtNative.activity().startActivity(sendIntent);
return true;
} else {
Log.d("ekkescorner share", "Intent not resolved");
}
return false;
}
// thx @oxied and @pooks for the idea: https://stackoverflow.com/a/18835895/135559
// theIntent is already configured with all needed properties and flags
// so we only have to add the packageName of targeted app
public static boolean createCustomChooserAndStartActivity(Intent theIntent, String title, int requestId, Uri uri) {
final Context context = QtNative.activity();
final PackageManager packageManager = context.getPackageManager();
final boolean isLowerOrEqualsKitKat = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT;
// MATCH_DEFAULT_ONLY: Resolution and querying flag. if set, only filters that support the CATEGORY_DEFAULT will be considered for matching.
// Check if there is a default app for this type of content.
ResolveInfo defaultAppInfo = packageManager.resolveActivity(theIntent, PackageManager.MATCH_DEFAULT_ONLY);
if(defaultAppInfo == null) {
Log.d("ekkescorner", title+" PackageManager cannot resolve Activity");
return false;
}
// had to remove this check - there can be more Activity names, per ex
// com.google.android.apps.docs.editors.kix.quickword.QuickWordDocumentOpenerActivityAlias
// if (!defaultAppInfo.activityInfo.name.endsWith("ResolverActivity") && !defaultAppInfo.activityInfo.name.endsWith("EditActivity")) {
// Log.d("ekkescorner", title+" defaultAppInfo not Resolver or EditActivity: "+defaultAppInfo.activityInfo.name);
// return false;
//}
// Retrieve all apps for our intent. Check if there are any apps returned
List<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;
}
// I am deleting the files from shared folder when Activity was done or canceled
// so probably I don't have to revike FilePermissions for older OS
// if you don't delete or move the file: here's what you must done to revoke the access
public static void revokeFilePermissions(String filePath) {
final Context context = QtNative.activity();
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
File file = new File(filePath);
Uri uri = FileProvider.getUriForFile(context, AUTHORITY, file);
context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
}
public static boolean sendFile(String filePath, String title, String mimeType, int requestId) {
if (QtNative.activity() == null)
return false;
// using v4 support library create the Intent from ShareCompat
// Intent sendIntent = new Intent();
Intent sendIntent = ShareCompat.IntentBuilder.from(QtNative.activity()).getIntent();
sendIntent.setAction(Intent.ACTION_SEND);
File imageFileToShare = new File(filePath);
// Using FileProvider you must get the URI from FileProvider using your AUTHORITY
// Uri uri = Uri.fromFile(imageFileToShare);
Uri uri;
try {
uri = FileProvider.getUriForFile(QtNative.activity(), AUTHORITY, imageFileToShare);
} catch (IllegalArgumentException e) {
Log.d("ekkescorner sendFile - cannot be shared: ", filePath);
return false;
}
Log.d("ekkescorner sendFile", uri.toString());
sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
if(mimeType == null || mimeType.isEmpty()) {
// fallback if mimeType not set
mimeType = QtNative.activity().getContentResolver().getType(uri);
Log.d("ekkescorner sendFile guessed mimeType:", mimeType);
} else {
Log.d("ekkescorner sendFile w mimeType:", mimeType);
}
sendIntent.setType(mimeType);
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
QtNative.activity().startActivity(Intent.createChooser(sendIntent, null));
return true;
}
public static boolean viewFile(String filePath, String title, String mimeType, int requestId) {
if (QtNative.activity() == null)
return false;
// using v4 support library create the Intent from ShareCompat
// Intent viewIntent = new Intent();
Intent viewIntent = ShareCompat.IntentBuilder.from(QtNative.activity()).getIntent();
viewIntent.setAction(Intent.ACTION_VIEW);
File imageFileToShare = new File(filePath);
// Using FileProvider you must get the URI from FileProvider using your AUTHORITY
// Uri uri = Uri.fromFile(imageFileToShare);
Uri uri;
try {
uri = FileProvider.getUriForFile(QtNative.activity(), AUTHORITY, imageFileToShare);
} catch (IllegalArgumentException e) {
Log.d("ekkescorner viewFile - cannot be shared: ", filePath);
return false;
}
// now we got a content URI per ex
// content://org.ekkescorner.examples.sharex.fileprovider/my_shared_files/qt-logo.png
// from a fileUrl:
// /data/user/0/org.ekkescorner.examples.sharex/files/share_example_x_files/qt-logo.png
Log.d("ekkescorner viewFile from file path: ", filePath);
Log.d("ekkescorner viewFile to content URI: ", uri.toString());
if(mimeType == null || mimeType.isEmpty()) {
// fallback if mimeType not set
mimeType = QtNative.activity().getContentResolver().getType(uri);
Log.d("ekkescorner viewFile guessed mimeType:", mimeType);
} else {
Log.d("ekkescorner viewFile w mimeType:", mimeType);
}
viewIntent.setDataAndType(uri, mimeType);
viewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
viewIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
return createCustomChooserAndStartActivity(viewIntent, title, requestId, uri);
}
public static boolean editFile(String filePath, String title, String mimeType, int requestId) {
if (QtNative.activity() == null)
return false;
// using v4 support library create the Intent from ShareCompat
// Intent editIntent = new Intent();
Intent editIntent = ShareCompat.IntentBuilder.from(QtNative.activity()).getIntent();
editIntent.setAction(Intent.ACTION_EDIT);
File imageFileToShare = new File(filePath);
// Using FileProvider you must get the URI from FileProvider using your AUTHORITY
// Uri uri = Uri.fromFile(imageFileToShare);
Uri uri;
try {
uri = FileProvider.getUriForFile(QtNative.activity(), AUTHORITY, imageFileToShare);
} catch (IllegalArgumentException e) {
Log.d("ekkescorner editFile - cannot be shared: ", filePath);
return false;
}
Log.d("ekkescorner editFile", uri.toString());
if(mimeType == null || mimeType.isEmpty()) {
// fallback if mimeType not set
mimeType = QtNative.activity().getContentResolver().getType(uri);
Log.d("ekkescorner editFile guessed mimeType:", mimeType);
} else {
Log.d("ekkescorner editFile w mimeType:", mimeType);
}
editIntent.setDataAndType(uri, mimeType);
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
return createCustomChooserAndStartActivity(editIntent, title, requestId, uri);
}
public static String getContentName(ContentResolver cR, Uri uri) {
Cursor cursor = cR.query(uri, null, null, null, null);
cursor.moveToFirst();
int nameIndex = cursor
.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
if (nameIndex >= 0) {
return cursor.getString(nameIndex);
} else {
return null;
}
}
public static String createFile(ContentResolver cR, Uri uri, String fileLocation) {
String filePath = null;
try {
InputStream iStream = cR.openInputStream(uri);
if (iStream != null) {
String name = getContentName(cR, uri);
if (name != null) {
filePath = fileLocation + "/" + name;
Log.d("ekkescorner - create File", filePath);
File f = new File(filePath);
FileOutputStream tmp = new FileOutputStream(f);
Log.d("ekkescorner - create File", "new FileOutputStream");
byte[] buffer = new byte[1024];
while (iStream.read(buffer) > 0) {
tmp.write(buffer);
}
tmp.close();
iStream.close();
return filePath;
} // name
} // iStream
} catch (FileNotFoundException e) {
e.printStackTrace();
return filePath;
} catch (IOException e) {
e.printStackTrace();
return filePath;
} catch (Exception e) {
e.printStackTrace();
return filePath;
}
return filePath;
}
}

View file

@ -1,8 +1,18 @@
QT += quick qml quickcontrols2
QT += quick qml quickcontrols2 purchasing widgets
CONFIG += c++11
VERSION = 0.8.0
TARGET = blueROCK
VERSION = 0.04
android {
QT += androidextras
android: include(/home/dorian/Android/Sdk/android_openssl/openssl.pri)
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android-sources
}
ios {
QMAKE_ASSET_CATALOGS += resources/shared/Assets.xcassets
xcode_product_bundle_identifier_setting.value = "de.itsblue.bluerock"
}
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
@ -15,30 +25,62 @@ DEFINES += QT_DEPRECATED_WARNINGS
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
INCLUDEPATH += $$PWD/headers
# Add version to define
DEFINES += APP_VERSION=\"\\\"$${VERSION}\\\"\"
SOURCES += \
sources/shareUtils/platformshareutils.cpp \
sources/brathlete.cpp \
sources/brleague.cpp \
sources/appsettings.cpp \
sources/bluerockbackend.cpp \
sources/shareUtils/shareutils.cpp \
sources/main.cpp
sources/brcalendar.cpp \
sources/competition/brround.cpp \
sources/brseason.cpp \
sources/brserverconnector.cpp \
sources/brwidget.cpp \
sources/competition/result/brresultdetailsgeneralresult.cpp \
sources/main.cpp \
sources/brcontroller.cpp \
sources/provider/brprovider.cpp \
sources/provider/brproviderdr.cpp \
sources/provider/brprovidervl.cpp \
sources/competition/brcategory.cpp \
sources/competition/brcompetition.cpp \
sources/competition/result/brresult.cpp \
sources/competition/result/brresultdetails.cpp \
sources/competition/result/brresultdetailsunknown.cpp \
sources/cup/brcup.cpp
HEADERS += \
headers/brathlete.h \
headers/brleague.h \
headers/appsettings.h \
headers/bluerockbackend.h \
headers/shareUtils/shareutils.h \
headers/shareUtils/platformshareutils.h
headers/brcalendar.h \
headers/competition/brcategory.h \
headers/competition/brcompetition.h \
headers/brcontroller.h \
headers/brseason.h \
headers/brserverconnector.h \
headers/brwidget.h \
headers/competition/result/brresultdetailsgeneralresult.h \
headers/provider/brprovider.h \
headers/provider/brproviderdr.h \
headers/provider/brprovidervl.h \
headers/competition/brround.h \
headers/competition/result/brresult.h \
headers/competition/result/brresultdetails.h \
headers/competition/result/brresultdetailsunknown.h \
headers/cup/brcup.h
INCLUDEPATH += \
headers \
headers/provider \
headers/competition \
headers/competition/result \
headers/cup
RESOURCES += resources/qml/qml.qrc \
resources/shared/shared.qrc \
resources/translations/translations.qrc
#resources/shared/icons/bluerock/index.theme \
#$$files(resources/shared/icons/*.png, true)
TRANSLATIONS += resources/translations/en.ts \
resources/translations/de.ts
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
@ -53,101 +95,19 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
DISTFILES += \
CHANGELOG.md \
README.md
android-sources/AndroidManifest.xml \
android-sources/build.gradle \
android-sources/gradle/wrapper/gradle-wrapper.jar \
android-sources/gradle/wrapper/gradle-wrapper.properties \
android-sources/gradlew \
android-sources/gradlew.bat \
android-sources/res/values/libs.xml \
resources/shared/icons/bluerock/index.theme \
$$files(resources/shared/icons/*.png, true)
android {
QT += androidextras
ANDROID_ABIS = armeabi-v7a
SOURCES += sources/shareUtils/androidshareutils.cpp
HEADERS += headers/shareUtils/androidshareutils.h
OTHER_FILES += android/src/org/ekkescorner/utils/QShareUtils.java \
android/src/org/ekkescorner/utils/QSharePathResolver.java \
android/src/de/itsblue/blueROCK/MainActivity.java \
android/AndroidManifest.xml \
android/build.gradle \
android/gradle.properties \
android/gradle/wrapper/gradle-wrapper.jar \
android/gradle/wrapper/gradle-wrapper.properties \
android/gradlew \
android/gradlew.bat \
android/res/values/libs.xml \
android/res/xml/filepaths.xml
defineReplace(droidVersionCode) {
segments = $$split(1, ".")
for (segment, segments): vCode = "$$first(vCode)$$format_number($$segment, width=3 zeropad)"
contains(ANDROID_TARGET_ARCH, arm64-v8a): \
suffix = 1
else:contains(ANDROID_TARGET_ARCH, armeabi-v7a): \
suffix = 0
# add more cases as needed
return($$first(vCode)$$first(suffix))
}
ANDROID_VERSION_NAME = $$VERSION
ANDROID_VERSION_CODE = $$droidVersionCode($$ANDROID_VERSION_NAME)
ANDROID_TARGET_SDK_VERSION = 31
ANDROID_HOME = $$(ANDROID_HOME)
include($$ANDROID_HOME/android_openssl/openssl.pri)
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
contains(ANDROID_TARGET_ARCH,armeabi-v7a) {
ANDROID_ABIS = \
armeabi-v7a
}
ios {
QT += purchasing
OBJECTIVE_SOURCES += sources/shareUtils/ios/iosshareutils.mm \
sources/iospermissionutils.mm \
sources/shareUtils/ios/docviewcontroller.mm
HEADERS += headers/shareUtils/ios/iosshareutils.h \
headers/iospermissionutils.h \
headers/shareUtils/ios/docviewcontroller.h
OTHER_FILES += ios/Info.plist \
ios/blueROCK.entitlements
QMAKE_INFO_PLIST = ios/Info.plist
#QMAKE_IOS_DEPLOYMENT_TARGET = 12.0
#disable_warning.name = GCC_WARN_64_TO_32_BIT_CONVERSION
#disable_warning.value = NO
#QMAKE_MAC_XCODE_SETTINGS += disable_warning
# see https://bugreports.qt.io/browse/QTCREATORBUG-16968
# ios_signature.pri not part of project repo because of private signature details
# contains:
# QMAKE_XCODE_CODE_SIGN_IDENTITY = "iPhone Developer"
# MY_DEVELOPMENT_TEAM.name = DEVELOPMENT_TEAM
# MY_DEVELOPMENT_TEAM.value = your team Id from Apple Developer Account
# QMAKE_MAC_XCODE_SETTINGS += MY_DEVELOPMENT_TEAM
#include(ios_signature.pri)
QMAKE_ASSET_CATALOGS += resources/shared/Assets.xcassets
MY_ENTITLEMENTS.name = CODE_SIGN_ENTITLEMENTS
MY_ENTITLEMENTS.value = $$PWD/ios/blueROCK.entitlements
QMAKE_MAC_XCODE_SETTINGS += MY_ENTITLEMENTS
MY_BUNDLE_ID.name = PRODUCT_BUNDLE_IDENTIFIER
MY_BUNDLE_ID.value = de.itsblue.bluerock
QMAKE_MAC_XCODE_SETTINGS += MY_BUNDLE_ID
xcode_product_bundle_identifier_setting.value = "de.itsblue.bluerock"
PRODUCT_IDENTIFIER = de.itsblue.bluerock
}
CONFIG += enable_decoder_qr_code \
enable_encoder_qr_code \
qzxing_multimedia \
qzxing_qml
include(qzxing/src/QZXing-components.pri)
# this has to be the last line!
ANDROID_ABIS = armeabi-v7a arm64-v8a

View file

@ -1,2 +0,0 @@
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("com.itsblue.speedclimbing_stopwatch") # e.g. com.krausefx.app

View file

@ -1,38 +0,0 @@
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:android)
platform :android do
desc "Runs all the tests"
lane :test do
gradle(task: "test")
end
desc "Submit a new Beta Build to Crashlytics Beta"
lane :beta do
gradle(task: "clean assembleRelease")
crashlytics
# sh "your_script.sh"
# You can also use other beta testing services here
end
desc "Deploy a new version to the Google Play"
lane :deploy do
gradle(task: "clean assembleRelease")
upload_to_play_store
end
end

View file

@ -10,26 +10,33 @@ class AppSettings : public QObject
{
Q_OBJECT
public:
explicit AppSettings(QObject *parent = nullptr);
// This is the Constructor of the AppSettings class
// This is the Constructor of the AppSettings class
~AppSettings();
// This is the Destructor of the AppSettings class
// This is the Destructor of the AppSettings class
private:
QSettings *settingsManager;
// QSettings object which cares about our settings.ini file
// QSettings object which cares about our settings.ini file
QSettings *themeSettingsManager;
// QSettings object which cares about the themes
signals:
void themeChanged();
public slots:
Q_INVOKABLE QString read(const QString &key);
// function to read values from the settings file
// function to read values from the settings file
Q_INVOKABLE void write(const QString &key, const QVariant &value);
// function to write values to the settings file
// function to write values to the settings file
Q_INVOKABLE void setDefault(const QString &key, const QVariant &defaultValue);
// function to create a key (/ setting) with a default value if it hasnt been ceated yet
// function to create a key (/ setting) with a default value if it hasnt been ceated yet
};
#endif // APPSETTINGS_H

View file

@ -1,81 +0,0 @@
/*
blueROCK - for digital rock
Copyright (C) 2019 Dorian Zedler
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SERVERCONN_H
#define SERVERCONN_H
#include <QObject>
#include <QtNetwork>
#include <QEventLoop>
#include <QTimer>
#include <QUrl>
#include <QPainter>
#include <QPixmap>
#include <QBrush>
#include <QTextCharFormat>
#include <QTextDocument>
#include <QTextCursor>
#include <QPageSize>
#include <QPdfWriter>
#include <QSizeF>
#include "QZXing.h"
#ifdef Q_OS_ANDROID
#include <QtAndroidExtras>
#elif defined Q_OS_IOS
#include "iospermissionutils.h"
#endif
#include "shareUtils/shareutils.h"
class BlueRockBackend : public QObject
{
Q_OBJECT
public:
explicit BlueRockBackend(QObject *parent = nullptr);
private:
QVariantMap _senddata(QUrl serviceUrl, QUrlQuery pdata = QUrlQuery());
ShareUtils* _shareUtils;
#ifdef Q_OS_IOS
IosPermissionUtils* _iosPermissionUtils;
#endif
const QStringList _validBaseDomains = {"digitalrock.de", "bluerock.dev"};
bool _pendingIntentsChecked;
signals:
Q_INVOKABLE void openedViaUrl(QString url, QString scheme);
public slots:
Q_INVOKABLE QVariant getWidgetData(QVariantMap params);
Q_INVOKABLE QVariantMap getParamsFromUrl(QString url);
Q_INVOKABLE void shareResultsAsUrl(QString url, QString compName);
Q_INVOKABLE void shareResultsAsPoster(QString url, QString compName);
Q_INVOKABLE bool isCameraPermissionGranted();
Q_INVOKABLE bool requestCameraPermission();
#if defined(Q_OS_ANDROID)
void onApplicationStateChanged(Qt::ApplicationState applicationState);
#endif
};
#endif // SERVERCONN_H

73
headers/brathlete.h Normal file
View file

@ -0,0 +1,73 @@
#ifndef BRATHLETE_H
#define BRATHLETE_H
#include <QObject>
#include <QUrl>
#include "brwidget.h"
class BRAthlete : public BRWidget
{
Q_OBJECT
Q_PROPERTY(QString firstName READ getFirstName NOTIFY metadataChanged)
Q_PROPERTY(QString lastName READ getLastName NOTIFY metadataChanged)
Q_PROPERTY(QString city READ getCity NOTIFY metadataChanged)
Q_PROPERTY(QString federation READ getFederation NOTIFY metadataChanged)
Q_PROPERTY(QUrl federationUrl READ getFederationUrl NOTIFY metadataChanged)
Q_PROPERTY(int yearOfBirth READ getYearOfBirth NOTIFY metadataChanged)
Q_PROPERTY(BRAthleteGender gender READ getGender NOTIFY metadataChanged)
Q_PROPERTY(QString nation READ getNation NOTIFY metadataChanged)
Q_PROPERTY(int age READ getAge NOTIFY metadataChanged)
public:
friend class BRProvider;
enum BRAthleteGender {
Unknown = -1,
Male,
Female
};
typedef struct {
const BRAthlete* athlete;
QString firstName;
QString lastName;
QString city;
QString federation;
QUrl federationUrl;
int yearOfBirth;
BRAthleteGender gender;
QString nation;
int age;
} BRAthleteData;
BRWidget::BRWidgetStatusCode load() override;
QString getFirstName();
QString getLastName();
QString getCity();
QString getFederation();
QUrl getFederationUrl();
int getYearOfBirth();
BRAthleteGender getGender();
QString getNation();
int getAge();
private:
BRAthlete(BRProvider* provider, BRWidget::BRFederation federation, int id, BRAthleteData initialData);
void setData(BRAthleteData data);
QString firstName;
QString lastName;
QString city;
QString federation;
QUrl federationUrl;
int yearOfBirth;
BRAthleteGender gender;
QString nation;
int age;
signals:
void metadataChanged();
};
#endif // BRATHLETE_H

48
headers/brcalendar.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef BRCALENDAR_H
#define BRCALENDAR_H
#include <QObject>
#include "brwidget.h"
#include "brseason.h"
#include "brcompetition.h"
#include "brcategory.h"
#include "brcup.h"
class BRCalendar : public BRWidget
{
Q_OBJECT
Q_PROPERTY(QString title READ getTitle NOTIFY titleChanged)
Q_PROPERTY(QList<QObject*> seasons READ getSeasonsQML NOTIFY seasonsChanged)
Q_PROPERTY(BRSeason* currentSeason READ getCurrentSeason WRITE setCurrentSeason NOTIFY currentSeasonChanged)
public:
friend class BRProvider;
typedef struct {
BRCalendar* calendar;
BRSeason* currentSeason;
QList<BRSeason*> seasons;
} BRCalendarData;
Q_INVOKABLE BRWidget::BRWidgetStatusCode load() override;
Q_INVOKABLE QString getTitle();
Q_INVOKABLE QList<QObject*> getSeasonsQML();
Q_INVOKABLE BRSeason* getCurrentSeason();
Q_INVOKABLE void setCurrentSeason(BRSeason* season);
private:
explicit BRCalendar(BRProvider* provider, BRFederation federation);
void setData(BRCalendarData data);
QList<BRSeason*> seasons;
BRSeason* currentSeason;
signals:
void titleChanged();
void seasonsChanged();
void currentSeasonChanged();
};
#endif // BRCALENDAR_H

28
headers/brcontroller.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef BRCONTROLLER_H
#define BRCONTROLLER_H
#include <QObject>
#include "brproviderdr.h"
#include "brprovidervl.h"
#include "brwidget.h"
class BRCalendar;
class BRController : public QObject
{
Q_OBJECT
public:
explicit BRController(QObject *parent = nullptr);
Q_INVOKABLE BRCalendar* getCalendar(BRWidget::BRFederation federation);
private:
BRProvider* providerDr;
BRProvider* providerVl;
signals:
};
#endif // BRCONTROLLER_H

62
headers/brleague.h Normal file
View file

@ -0,0 +1,62 @@
#ifndef BRLEAGUE_H
#define BRLEAGUE_H
#include <QObject>
#include <QColor>
#include "brwidget.h"
#include "brcompetition.h"
#include "brcategory.h"
#include "brcup.h"
class BRLeague : public BRWidget
{
Q_OBJECT
Q_PROPERTY(QString name READ getName NOTIFY metadataChanged)
Q_PROPERTY(QColor color READ getColor NOTIFY metadataChanged)
Q_PROPERTY(bool enabled READ getEnabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(QList<QObject*> competitions READ getCompetitionsQML NOTIFY competitionsChanged)
Q_PROPERTY(QList<QObject*> cups READ getCupsQML NOTIFY cupsChanged)
public:
friend class BRProvider;
typedef struct {
const BRLeague* league;
QString name;
QColor color;
bool enabled;
QList<BRCompetition*> competitions;
QList<BRCup*> cups;
} BRLeagueData;
BRWidget::BRWidgetStatusCode load() override;
Q_INVOKABLE QString getName();
Q_INVOKABLE QColor getColor();
Q_INVOKABLE bool getEnabled();
Q_INVOKABLE void setEnabled(bool enabled);
Q_INVOKABLE QList<BRCompetition*> getCompetitions();
Q_INVOKABLE QList<QObject*> getCompetitionsQML();
Q_INVOKABLE QList<QObject*> getCupsQML();
private:
BRLeague(BRProvider* provider, BRWidget::BRFederation federation, int id, BRLeagueData initialData);
void setData(BRLeagueData data);
QString name;
QColor color;
bool enabled;
QList<BRCompetition*> competitions;
QList<BRCup*> cups;
signals:
void metadataChanged();
void enabledChanged();
void competitionsChanged();
void cupsChanged();
};
#endif // BRLEAGUE_H

60
headers/brseason.h Normal file
View file

@ -0,0 +1,60 @@
#ifndef BRSEASON_H
#define BRSEASON_H
#include <QObject>
#include "brwidget.h"
#include "brleague.h"
class BRSeason : public BRWidget
{
Q_OBJECT
Q_PROPERTY(int year READ getYear NOTIFY metadataChanged)
Q_PROPERTY(QString name READ getName NOTIFY metadataChanged)
Q_PROPERTY(QList<QObject*> leagues READ getLeaguesQML NOTIFY leaguesChanged)
Q_PROPERTY(QList<QObject*> competitions READ getCompetitionsQML NOTIFY competitionsChanged)
Q_PROPERTY(QList<QObject*> cups READ getCupsQML NOTIFY cupsChanged)
public:
friend class BRProvider;
typedef struct {
const BRSeason* season;
int year;
QString name;
bool nativeMultiLeagueSupport;
QList<BRLeague*> leagues;
} BRSeasonData;
BRWidget::BRWidgetStatusCode load() override;
Q_INVOKABLE int getYear() const;
Q_INVOKABLE QString getName();
Q_INVOKABLE QList<QObject*> getLeaguesQML();
QList<BRLeague*> getLeagues() const;
Q_INVOKABLE QList<QObject*> getCompetitionsQML();
Q_INVOKABLE QList<QObject*> getCupsQML();
private:
explicit BRSeason(BRProvider* provider, BRWidget::BRFederation federation, int id, BRSeasonData initialData);
void setData(BRSeasonData data);
int year;
QString name;
bool multiLeagueSupport;
QList<BRLeague*> leagues;
signals:
void metadataChanged();
void leaguesChanged();
void competitionsChanged();
void cupsChanged();
};
#endif // BRSEASON_H

View file

@ -0,0 +1,45 @@
/*
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/>.
*/
#ifndef SERVERCONN_H
#define SERVERCONN_H
#include <QObject>
#include <QtNetwork>
#include <QEventLoop>
#include <QTimer>
#include <QUrl>
class BRServerConnector : public QObject
{
Q_OBJECT
public:
explicit BRServerConnector(QObject *parent = nullptr);
private:
QVariantMap senddata(QUrl serviceUrl, QUrlQuery pdata = QUrlQuery());
signals:
public slots:
QVariant getWidgetData(QVariantMap params);
};
#endif // SERVERCONN_H

83
headers/brwidget.h Normal file
View file

@ -0,0 +1,83 @@
#ifndef BRWIDGET_H
#define BRWIDGET_H
#include <QObject>
class BRProvider;
class BRWidget : public QObject
{
Q_OBJECT
Q_PROPERTY(BRWidgetState state READ getState NOTIFY stateChanged)
public:
enum BRFederation {
UnknownFederation = -1,
IFSC,
DAV,
SAC
};
Q_ENUM(BRFederation)
enum BRDiscipline {
UnknownDiscipline = -1,
Speed,
Lead,
Boulder,
Combined,
AllDisciplines = 99
};
Q_ENUM(BRDiscipline)
enum BRWidgetState {
Unconfigured = -1,
Configured,
Loading,
Loaded
};
Q_ENUM(BRWidgetState)
enum BRWidgetStatusCode {
Success = 200,
InternalError = 900,
NoProviderError = 901,
NotImplementedError = 902,
OpeationNotSupportedError = 903
};
Q_ENUM(BRWidgetStatusCode)
Q_INVOKABLE virtual BRWidget::BRWidgetStatusCode load() = 0;
Q_INVOKABLE BRWidgetState getState() const;
Q_INVOKABLE BRFederation getFederation() const;
Q_INVOKABLE int getId() const;
protected:
explicit BRWidget(BRProvider* provider, BRFederation federation, int id);
void setState(BRWidgetState state);
BRProvider* getProvider();
BRWidgetState state;
template<typename T>
QList<QObject*> listToQmlList(QList<T*> list) {
QList<QObject*> tmpList;
for(T* item : list)
tmpList.append(static_cast<QObject*>(item));
return tmpList;
};
private:
BRProvider* provider;
const BRFederation federation;
const int id;
signals:
void stateChanged();
};
#endif // BRWIDGET_H

View file

@ -0,0 +1,80 @@
#ifndef BRCATEGORY_H
#define BRCATEGORY_H
#include <QObject>
#include "brwidget.h"
#include "brround.h"
#include "brathlete.h"
#include "brresultdetailsgeneralresult.h"
class BRCompetition;
class BRCategory : public BRWidget
{
Q_OBJECT
Q_PROPERTY(QString name READ getName NOTIFY metadataChanged)
Q_PROPERTY(BRDiscipline discipline READ getDiscipline NOTIFY metadataChanged)
Q_PROPERTY(BRCategoryStatus status READ getStatus NOTIFY metadataChanged)
Q_PROPERTY(BRRound* currentRound READ getCurrentRound WRITE setCurrentRound NOTIFY currentRoundChanged)
Q_PROPERTY(BRRound* generalResult READ getGeneralResult NOTIFY generalResultChanged)
Q_PROPERTY(QList<QObject*> rounds READ getRoundsQML NOTIFY roundsChanged)
public:
friend class BRProvider;
friend class BRCompetition;
enum BRCategoryStatus {
UnknownStatus = -1,
Registration,
Startlist,
Result
};
Q_ENUM(BRCategoryStatus)
typedef struct {
const BRCategory* category;
QString name;
BRDiscipline discipline;
BRCategoryStatus status;
BRRound* currentRound;
BRRound* generalResult;
QList<BRRound*> rounds;
} BRCategoryData;
Q_INVOKABLE BRWidget::BRWidgetStatusCode load() override;
Q_INVOKABLE BRCompetition* getCompetition() const;
Q_INVOKABLE QString getName();
Q_INVOKABLE BRDiscipline getDiscipline();
Q_INVOKABLE BRCategoryStatus getStatus();
Q_INVOKABLE BRRound* getCurrentRound() const;
Q_INVOKABLE void setCurrentRound(BRRound* round = nullptr);
Q_INVOKABLE BRRound* getGeneralResult();
Q_INVOKABLE QList<BRRound*> getRounds(bool includeGeneralResult = false) const;
Q_INVOKABLE QList<QObject*> getRoundsQML();
BRCategoryData getData();
private:
BRCategory(BRProvider* provider, BRWidget::BRFederation federation, int id, BRCategoryData initialData);
void setData(BRCategoryData data);
BRCompetition* competition;
BRRound* currentRound;
QString name;
BRDiscipline discipline;
BRCategoryStatus status;
BRRound* generalResult;
QList<BRRound*> rounds;
signals:
void metadataChanged();
void currentRoundChanged();
void roundsChanged();
void generalResultChanged();
void resultsChanged();
};
#endif // BRCATEGORY_H

View file

@ -0,0 +1,94 @@
#ifndef BRCOMPETITION_H
#define BRCOMPETITION_H
#include <QObject>
#include <QDate>
#include <QUrl>
#include <QDebug>
#include "brwidget.h"
#include "brcategory.h"
class BRLeague;
class BRCompetition : public BRWidget
{
Q_OBJECT
Q_PROPERTY(QString name READ getName NOTIFY metadataChanged)
Q_PROPERTY(QDate startDate READ getStartDate NOTIFY metadataChanged)
Q_PROPERTY(QDate endDate READ getEndDate NOTIFY metadataChanged)
Q_PROPERTY(QString dateSpan READ getDateSpan NOTIFY metadataChanged)
Q_PROPERTY(BRLeague* league READ getLeague NOTIFY metadataChanged)
Q_PROPERTY(QUrl eventWebsiteUrl READ getEventWebsiteUrl NOTIFY metadataChanged)
Q_PROPERTY(QList<QUrl> infosheetUrls READ getInfosheetUrls NOTIFY metadataChanged)
Q_PROPERTY(bool pinned READ getPinned WRITE setPinned NOTIFY pinnedChanged)
Q_PROPERTY(QList<QObject*> categories READ getCategoriesQML NOTIFY categoriesChanged)
Q_PROPERTY(BRCategory* currentCategory READ getCurrentCategory WRITE setCurrentCategory NOTIFY currentCategoryChanged)
Q_PROPERTY(QList<QObject*> results READ getResultsQML NOTIFY resultsChanged)
public:
friend class BRProvider;
friend class BRLeague;
typedef struct BRCompetitionData {
const BRCompetition* competition;
QString name;
QDate startDate;
QDate endDate;
QUrl eventWebsiteUrl;
QList<QUrl> infosheetUrls;
bool pinned;
BRCategory* currentCategory;
QList<BRCategory*> categories;
} BRCompetitionData;
Q_INVOKABLE BRWidget::BRWidgetStatusCode load() override;
Q_INVOKABLE QString getName();
Q_INVOKABLE QDate getStartDate();
Q_INVOKABLE QDate getEndDate();
Q_INVOKABLE QString getDateSpan();
Q_INVOKABLE BRLeague* getLeague();
Q_INVOKABLE QUrl getEventWebsiteUrl();
Q_INVOKABLE QList<QUrl> getInfosheetUrls();
Q_INVOKABLE bool getPinned();
Q_INVOKABLE void setPinned(bool pinned);
Q_INVOKABLE QList<QObject*> getCategoriesQML();
Q_INVOKABLE QList<BRCategory*> getCategories();
Q_INVOKABLE BRCategory* getCurrentCategory() const;
Q_INVOKABLE void setCurrentCategory(BRCategory* category);
Q_INVOKABLE QList<QObject*> getResultsQML();
static bool lessThan(BRCompetition* competition1, BRCompetition* competition2);
private:
explicit BRCompetition(BRProvider* provider, BRWidget::BRFederation federation, int id, BRCompetitionData initialData);
void setLeague(BRLeague* league);
// metadata
QString name;
QDate startDate;
QDate endDate;
BRLeague* league;
QUrl eventWebsiteUrl;
QList<QUrl> infosheetUrls;
// data
bool pinned;
QList<BRCategory*> categories;
BRCategory* currentCategory;
void setData(BRCompetitionData data);
signals:
void metadataChanged();
void pinnedChanged();
void categoriesChanged();
void currentCategoryChanged();
void resultsChanged();
};
#endif // BRCOMPETITION_H

View file

@ -0,0 +1,56 @@
#ifndef BRROUND_H
#define BRROUND_H
#include <QObject>
#include "brwidget.h"
#include "brresult.h"
class BRCategory;
class BRRound : public BRWidget
{
Q_OBJECT
Q_PROPERTY(QString name READ getName NOTIFY metadataChanged)
Q_PROPERTY(QList<QObject*> results READ getResultsQML NOTIFY resultsChanged)
public:
friend class BRProvider;
friend class BRCategory;
typedef struct {
BRRound* round;
QString name;
QList<BRResult*> results;
} BRRoundData;
BRWidget::BRWidgetStatusCode load() override;
Q_INVOKABLE BRCategory* getCategory() const;
Q_INVOKABLE bool isGeneralResult() const;
Q_INVOKABLE QString getName();
Q_INVOKABLE QList<BRResult*> getResults();
Q_INVOKABLE QList<QObject*> getResultsQML();
BRRoundData getData();
static bool lessThan(BRRound* round1, BRRound* round2);
private:
BRRound(BRProvider* provider, BRWidget::BRFederation federation, int id, BRRoundData initialData, bool generalResult = false);
void setData(BRRoundData data);
BRCategory* category;
bool generalResult;
QString name;
QList<BRResult*> results;
signals:
void metadataChanged();
void resultsChanged();
};
#endif // BRROUND_H

View file

@ -0,0 +1,57 @@
#ifndef BRRESULT_H
#define BRRESULT_H
#include <QObject>
#include "brwidget.h"
#include "brresultdetails.h"
class BRAthlete;
class BRRound;
class BRResult : public BRWidget
{
Q_OBJECT
Q_PROPERTY(int rank READ getRank NOTIFY metadataChanged)
Q_PROPERTY(int startNumber READ getStartNumber NOTIFY metadataChanged)
Q_PROPERTY(BRResultDetails* details READ getDetails NOTIFY metadataChanged)
Q_PROPERTY(BRAthlete* athlete READ getAthlete NOTIFY metadataChanged)
public:
friend class BRProvider;
friend class BRRound;
typedef struct {
const BRResult* result;
int rank;
int startNumber;
BRResultDetails* details;
BRAthlete* athlete;
} BRResultData;
BRWidget::BRWidgetStatusCode load() override;
Q_INVOKABLE int getRank() const;
Q_INVOKABLE int getStartNumber() const;
Q_INVOKABLE BRResultDetails* getDetails() const;
Q_INVOKABLE BRAthlete* getAthlete() const;
Q_INVOKABLE BRRound* getRound() const;
static bool lessThan(BRResult* result1, BRResult* result2);
private:
BRResult(BRResultData initialData);
void setData(BRResultData data);
int rank;
int startNumber;
BRResultDetails* details;
BRAthlete* athlete;
BRRound* round;
signals:
void metadataChanged();
};
#endif // BRRESULT_H

View file

@ -0,0 +1,50 @@
#ifndef BRRESULTDATA_H
#define BRRESULTDATA_H
#include <QObject>
#include "brwidget.h"
class BRResult;
class BRResultDetails : public BRWidget
{
Q_OBJECT
Q_PROPERTY(BRResultDetailsType type READ getType NOTIFY metadataChanged)
public:
friend class BRResult;
enum BRResultDetailsType {
UnknownResultType = -1,
GeneralResult,
SpeedQualificationResult,
SpeedFinalResult,
LeadResult,
BoulderResult,
CombinedResult
};
BRWidget::BRWidgetStatusCode load() final override;
Q_INVOKABLE virtual QString toString() = 0;
Q_INVOKABLE virtual QString string() final;
Q_INVOKABLE BRResultDetailsType getType();
protected:
BRResultDetails(BRResultDetailsType type);
BRResult* getResult();
private:
BRResult* result;
BRResultDetailsType type;
BRWidget::BRDiscipline getDiscipline();
signals:
void metadataChanged();
};
#endif // BRRESULTDATA_H

View file

@ -0,0 +1,28 @@
#ifndef BRGENERALRESULT_H
#define BRGENERALRESULT_H
#include <QObject>
#include <QDebug>
#include "brresultdetails.h"
class BRResult;
class BRResultDetailsGeneralResult : public BRResultDetails
{
Q_OBJECT
Q_PROPERTY(QList<QObject*> results READ getResultsQML NOTIFY resultsChanged)
public:
BRResultDetailsGeneralResult();
QList<BRResult*> getResults();
QList<QObject*> getResultsQML();
Q_INVOKABLE QString toString() override;
private:
signals:
void resultsChanged();
};
#endif // BRGENERALRESULT_H

View file

@ -0,0 +1,27 @@
#ifndef BRRESULTDETAILSUNKNOWN_H
#define BRRESULTDETAILSUNKNOWN_H
#include <QObject>
#include "brresultdetails.h"
class BRResultDetailsUnknown : public BRResultDetails
{
Q_OBJECT
Q_PROPERTY(QString score READ toString NOTIFY metadataChanged())
public:
BRResultDetailsUnknown(QString score);
Q_INVOKABLE QString toString() {
return this->score;
}
private:
QString score;
BRDiscipline getType() {
return BRWidget::UnknownDiscipline;
}
};
#endif // BRRESULTDETAILSUNKNOWN_H

42
headers/cup/brcup.h Normal file
View file

@ -0,0 +1,42 @@
#ifndef BRCUP_H
#define BRCUP_H
#include <QObject>
#include <QDebug>
#include "brwidget.h"
#include "brcategory.h"
class BRCup : public BRWidget
{
Q_OBJECT
Q_PROPERTY(QString name READ getName NOTIFY metadataChanged)
Q_PROPERTY(QList<QObject*> categories READ getCategoriesQML NOTIFY categoriesChanged)
public:
friend class BRProvider;
typedef struct {
const BRCup* cup;
QString name;
QList<BRCategory*> categories;
} BRCupData;
Q_INVOKABLE BRWidget::BRWidgetStatusCode load() override;
Q_INVOKABLE QString getName();
Q_INVOKABLE QList<QObject*> getCategoriesQML();
private:
BRCup(BRProvider* provider, BRWidget::BRFederation federation, int id, BRCupData initialData);
void setData(BRCupData data);
QString name;
QList<BRCategory*> categories;
signals:
void metadataChanged();
void categoriesChanged();
};
#endif // BRCUP_H

View file

@ -1,22 +0,0 @@
#ifndef IOSPERMISSIONUTILS_H
#define IOSPERMISSIONUTILS_H
#include <QObject>
#include <QEventLoop>
class IosPermissionUtils : QObject
{
Q_OBJECT
public:
IosPermissionUtils();
bool isCameraPermissionGranted();
bool requestCameraPermission();
private:
QEventLoop* _responseWaitLoop;
signals:
void permissionRequestFinished(bool result);
};
#endif // IOSPERMISSIONUTILS_H

View file

@ -0,0 +1,65 @@
#ifndef BRPROVIDER_H
#define BRPROVIDER_H
#include <QObject>
#include <QtNetwork>
#include <QEventLoop>
#include <QTimer>
#include <QUrl>
#include "brwidget.h"
#include "brcalendar.h"
#include "brseason.h"
#include "brleague.h"
#include "brcompetition.h"
#include "brathlete.h"
#include "brcup.h"
#include "brresult.h"
#include "brresultdetailsunknown.h"
class BRProvider : public QObject
{
Q_OBJECT
public:
explicit BRProvider(QObject *parent = nullptr);
friend class BRController;
friend class BRCalendar;
friend class BRSeason;
friend class BRLeague;
friend class BRCompetition;
friend class BRCategory;
friend class BRAthlete;
friend class BRRound;
protected:
QVariantMap serverRequest(QUrl serviceUrl, QList<QStringList> additionalHeaders = {}, QUrlQuery postData = QUrlQuery());
virtual BRCalendar* getCalendar(BRWidget::BRFederation federation) final;
virtual BRSeason* getSeason(BRWidget::BRFederation federation, int id, BRSeason::BRSeasonData initialData) final;
virtual BRLeague* getLeague(BRWidget::BRFederation federation, int id, BRLeague::BRLeagueData initialData) final;
virtual BRCompetition* getCompetition(BRWidget::BRFederation federation, int id, BRCompetition::BRCompetitionData initialData) final;
virtual BRCup* getCup(BRWidget::BRFederation federation, int id, BRCup::BRCupData initialData) final;
virtual BRCategory* getCategory(BRWidget::BRFederation federation, int id, BRCategory::BRCategoryData initialData) final;
virtual BRAthlete* getAthlete(BRWidget::BRFederation federation, int id, BRAthlete::BRAthleteData initialData) final;
virtual BRRound* getRound(BRWidget::BRFederation federation, int id, BRRound::BRRoundData initialData, bool generalResult = false) final;
virtual BRResult* getResult(BRResult::BRResultData initialData) final;
virtual void setCategoryData(BRCategory* category, BRCategory::BRCategoryData data) final {
category->setData(data);
}
virtual void setRoundData(BRRound* round, BRRound::BRRoundData data) final {
round->setData(data);
}
virtual BRWidget::BRWidgetStatusCode getWidgetData(BRCalendar::BRCalendarData* calendarData) = 0;
virtual BRWidget::BRWidgetStatusCode getWidgetData(BRSeason::BRSeasonData* seasonData) = 0;
virtual BRWidget::BRWidgetStatusCode getWidgetData(BRLeague::BRLeagueData* leagueData) = 0;
virtual BRWidget::BRWidgetStatusCode getWidgetData(BRCompetition::BRCompetitionData* competitionData) = 0;
signals:
};
#endif // BRCONTROLLER_H

View file

@ -0,0 +1,44 @@
#ifndef BRPROVIDERDR_H
#define BRPROVIDERDR_H
#include <QObject>
#include "brprovider.h"
class BRProviderDr : public BRProvider
{
Q_OBJECT
public:
explicit BRProviderDr(QObject *parent = nullptr);
protected:
BRWidget::BRWidgetStatusCode getWidgetData(BRCalendar::BRCalendarData* calendarData) override;
BRWidget::BRWidgetStatusCode getWidgetData(BRSeason::BRSeasonData* seasonData) override;
BRSeason::BRSeasonData parseSeasonData(int id, QVariantMap rawData);
void parseSeasonData(BRSeason::BRSeasonData* seasonData, int id, QVariantMap rawData);
BRWidget::BRWidgetStatusCode getWidgetData(BRLeague::BRLeagueData* leagueData) override;
BRLeague::BRLeagueData parseLeagueData(QVariantMap leagueProperties, QVariantMap rawData);
BRWidget::BRWidgetStatusCode getWidgetData(BRCompetition::BRCompetitionData* competitionData) override;
BRCompetition::BRCompetitionData parseCompetitionData(QVariantMap rawData, QVariantList globalCategoriesList, BRWidget::BRFederation federation);
void parseCompetitionData(BRCompetition::BRCompetitionData* competitionData, QVariantMap rawData, BRWidget::BRFederation federation);
BRCup::BRCupData parseCupData(QVariantList categoriesList, QVariantMap rawData, BRWidget::BRFederation federation);
BRCategory::BRCategoryData parseCategoryData(QVariantMap rawData);
void parseCategoryData(BRCategory::BRCategoryData* categoryData, QVariantMap rawData);
void parseRoundData(BRRound::BRRoundData* roundData, QVariantMap rawData, QString resultKeyAttachment = "");
BRAthlete::BRAthleteData parseAthletedata(QVariantMap rawData);
BRResult::BRResultData parseResultData(QVariantMap rawData);
private:
QMap<BRWidget::BRFederation, QList<QVariantMap>> leagues;
};
#endif // BRPROVIDERDR_H

View file

@ -0,0 +1,39 @@
#ifndef BRPROVIDERVL_H
#define BRPROVIDERVL_H
#include <QObject>
#include "brprovider.h"
class BRProviderVl : public BRProvider
{
Q_OBJECT
public:
explicit BRProviderVl(QObject *parent = nullptr);
protected:
BRWidget::BRWidgetStatusCode getWidgetData(BRCalendar::BRCalendarData* calendarData) override;
BRWidget::BRWidgetStatusCode getWidgetData(BRSeason::BRSeasonData* seasonData) override;
BRSeason::BRSeasonData parseSeasonData(QVariantMap rawData);
BRWidget::BRWidgetStatusCode getWidgetData(BRLeague::BRLeagueData* leagueData) override;
void parseLeagueData(BRLeague::BRLeagueData* leagueData, QVariantMap rawData);
BRWidget::BRWidgetStatusCode getWidgetData(BRCompetition::BRCompetitionData* competitionData) override;
BRCompetition::BRCompetitionData parseCompetitionData(QVariantMap rawData, QVariantList globalCategoriesList, BRWidget::BRFederation federation);
BRCup::BRCupData parseCupData(QVariantMap rawData, QVariantList globalCategoriesList);
BRCategory::BRCategoryData parseCategoryData(QVariantMap rawData);
//void parseCategoryData(BRCategory::BRCategoryData* categoryData, QVariantMap rawData);
BRRound::BRRoundData parseRoundData(QVariantMap rawData);
void parseRoundData(BRRound::BRRoundData* roundData, QVariantMap rawData);
private:
QMap<BRWidget::BRFederation, QList<QVariantMap>> leagues;
};
#endif // BRPROVIDERVL_H

View file

@ -1,51 +0,0 @@
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
// my blog about Qt for mobile: http://j.mp/qt-x
// see also /COPYRIGHT and /LICENSE
#ifndef ANDROIDSHAREUTILS_H
#define ANDROIDSHAREUTILS_H
#include <QtAndroid>
#include <QAndroidActivityResultReceiver>
#include "shareUtils/platformshareutils.h"
class AndroidShareUtils : public PlatformShareUtils, public QAndroidActivityResultReceiver
{
Q_OBJECT
public:
AndroidShareUtils(QObject* parent = nullptr);
bool checkMimeTypeView(const QString &mimeType) override;
bool checkMimeTypeEdit(const QString &mimeType) override;
virtual QString getTemporaryFileLocationPath() override;
void shareText(const QString &text, const QUrl &url) override;
void sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
void viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
void editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override;
void onActivityResult(int requestCode, int resultCode);
void checkPendingIntents(const QString workingDirPath) override;
static AndroidShareUtils* getInstance();
public slots:
void setFileUrlReceived(const QString &url);
void setOtherUrlReceived(const QString &url, const QString &scheme);
void setFileReceivedAndSaved(const QString &url);
bool checkFileExits(const QString &url);
private:
bool mIsEditMode;
qint64 mLastModified;
QString mCurrentFilePath;
static AndroidShareUtils* mInstance;
void processActivityResult(int requestCode, int resultCode);
};
#endif // ANDROIDSHAREUTILS_H

View file

@ -1,21 +0,0 @@
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
// my blog about Qt for mobile: http://j.mp/qt-x
// see also /COPYRIGHT and /LICENSE
#ifndef DOCVIEWCONTROLLER_HPP
#define DOCVIEWCONTROLLER_HPP
#import <UIKit/UIKit.h>
#import "iosshareutils.h"
@interface DocViewController : UIViewController <UIDocumentInteractionControllerDelegate>
@property int requestId;
@property IosShareUtils *mIosShareUtils;
@end
#endif // DOCVIEWCONTROLLER_HPP

View file

@ -1,31 +0,0 @@
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
// my blog about Qt for mobile: http://j.mp/qt-x
// see also /COPYRIGHT and /LICENSE
#ifndef __IOSSHAREUTILS_H__
#define __IOSSHAREUTILS_H__
#include "headers/shareUtils/platformshareutils.h"
class IosShareUtils : public PlatformShareUtils
{
Q_OBJECT
public:
explicit IosShareUtils(QObject *parent = 0);
bool checkMimeTypeView(const QString &mimeType) override;
bool checkMimeTypeEdit(const QString &mimeType) override;
void shareText(const QString &text, const QUrl &url) override;
void sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
void viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
void editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId) override;
void handleDocumentPreviewDone(const int &requestId);
public slots:
void handleFileUrlReceived(const QUrl &url);
void handleHttpsUrlReceived(const QUrl &url);
};
#endif

View file

@ -1,51 +0,0 @@
// (c) 2017 Ekkehard Gentz (ekke)
// this project is based on ideas from
// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
// see github project https://github.com/lasconic/ShareUtils-QML
// also inspired by:
// https://www.androidcode.ninja/android-share-intent-example/
// https://www.calligra.org/blogs/sharing-with-qt-on-android/
// https://stackoverflow.com/questions/7156932/open-file-in-another-app
// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
// see also /COPYRIGHT and /LICENSE
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
// my blog about Qt for mobile: http://j.mp/qt-x
// see also /COPYRIGHT and /LICENSE
#ifndef PLATFORMSHAREUTILS_H
#define PLATFORMSHAREUTILS_H
#include <QObject>
#include <QDebug>
#include <QStandardPaths>
#include <QDesktopServices>
#include <QUrl>
class PlatformShareUtils : public QObject
{
Q_OBJECT
signals:
void shareEditDone(int requestCode);
void shareFinished(int requestCode);
void shareNoAppAvailable(int requestCode);
void shareError(int requestCode, QString message);
void fileUrlReceived(QString url);
void otherUrlReceived(QString url, QString scheme);
void fileReceivedAndSaved(QString url);
public:
PlatformShareUtils(QObject *parent = 0);
virtual ~PlatformShareUtils();
virtual bool checkMimeTypeView(const QString &mimeType);
virtual bool checkMimeTypeEdit(const QString &mimeType);
virtual QString getTemporaryFileLocationPath();
virtual void shareText(const QString &text, const QUrl &url);
virtual void sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
virtual void viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
virtual void editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
virtual void checkPendingIntents(const QString workingDirPath);
};
#endif // PLATFORMSHAREUTILS_H

View file

@ -1,63 +0,0 @@
// (c) 2017 Ekkehard Gentz (ekke)
// this project is based on ideas from
// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
// see github project https://github.com/lasconic/ShareUtils-QML
// also inspired by:
// https://www.androidcode.ninja/android-share-intent-example/
// https://www.calligra.org/blogs/sharing-with-qt-on-android/
// https://stackoverflow.com/questions/7156932/open-file-in-another-app
// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
// see also /COPYRIGHT and /LICENSE
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
// my blog about Qt for mobile: http://j.mp/qt-x
// see also /COPYRIGHT and /LICENSE
#ifndef SHAREUTILS_H
#define SHAREUTILS_H
#include <QObject>
#include <QDebug>
#include "shareUtils/platformshareutils.h"
class ShareUtils : public QObject
{
Q_OBJECT
signals:
void shareEditDone(int requestCode);
void shareFinished(int requestCode);
void shareNoAppAvailable(int requestCode);
void shareError(int requestCode, QString message);
void fileUrlReceived(QString url);
void otherUrlReceived(QString url, QString scheme);
void fileReceivedAndSaved(QString url);
public slots:
void onShareEditDone(int requestCode);
void onShareFinished(int requestCode);
void onShareNoAppAvailable(int requestCode);
void onShareError(int requestCode, QString message);
void onFileUrlReceived(QString url);
void onOtherUrlReceived(QString url, QString scheme);
void onFileReceivedAndSaved(QString url);
public:
explicit ShareUtils(QObject *parent = 0);
Q_INVOKABLE bool checkMimeTypeView(const QString &mimeType);
Q_INVOKABLE bool checkMimeTypeEdit(const QString &mimeType);
Q_INVOKABLE QString getTemporaryFileLocationPath();
Q_INVOKABLE void shareText(const QString &text, const QUrl &url);
Q_INVOKABLE void sendFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
Q_INVOKABLE void viewFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
Q_INVOKABLE void editFile(const QString &filePath, const QString &title, const QString &mimeType, const int &requestId);
Q_INVOKABLE void checkPendingIntents(const QString workingDirPath);
private:
PlatformShareUtils* mPlatformShareUtils;
};
#endif //SHAREUTILS_H

View file

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>${ASSETCATALOG_COMPILER_APPICON_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${QMAKE_SHORT_VERSION}</string>
<key>CFBundleSignature</key>
<string>${QMAKE_PKGINFO_TYPEINFO}</string>
<key>CFBundleVersion</key>
<string>${QMAKE_FULL_VERSION}</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>${IPHONEOS_DEPLOYMENT_TARGET}</string>
<key>NOTE</key>
<string>This file was generated by Qt/QMake.</string>
<key>NSCameraUsageDescription</key>
<string>blueROCK would like to access the camera.</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:l.bluerock.dev</string>
</array>
</dict>
</plist>

1
qzxing

@ -1 +0,0 @@
Subproject commit 6ea2b31e26db9d43db027ba207f5c73dc9d759fc

View file

@ -1,21 +0,0 @@
import QtQuick 2.0
import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12
Button {
id: control
property alias horizontalAlignment: label.horizontalAlignment
property alias verticalAlignment: label.verticalAlignment
contentItem: Label {
id: label
text: control.text
font: control.font
color: !control.enabled ? control.Material.hintTextColor :
control.flat && control.highlighted ? control.Material.accentColor :
control.highlighted ? control.Material.primaryHighlightedTextColor : control.Material.foreground
}
}

View file

@ -37,7 +37,7 @@ Item {
Rectangle {
id: toolBar
color: Material.background
color: "white"
anchors.fill: parent
Rectangle {

View file

@ -1,32 +0,0 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.0
RowLayout {
id: control
Image {
Layout.preferredHeight: parent.height
Layout.preferredWidth: height
Layout.alignment: Layout.Center
fillMode: Image.PreserveAspectFit
mipmap: true
source: "qrc:/icons/blueRockHold.png"
}
Label {
Layout.preferredHeight: parent.height
Layout.fillWidth: true
Layout.alignment: Layout.Center
fontSizeMode: Text.Fit
font.pixelSize: parent.height * 0.6
font.bold: true
verticalAlignment: Text.AlignVCenter
text: "blueROCK"
}
}

View file

@ -1,262 +0,0 @@
import QtQuick 2.10
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.15
Row {
id: multiResRow
property bool active: parseInt(widgetData[ "route_order" ]) > -1 && boulderResRep.model > 0
height: parent.height
width: active ? parent.width * 0.75:0
enabled: parseInt(widgetData[ "route_order" ]) > -1 && boulderResRep.model > 0
Repeater {
id: boulderResRep
model: parseInt(widgetData[ "route_num_problems" ])
function getDataForIcon(index){
// TODO: clean
var resultString = widgetData[ "participants" ][partDel.thisIndex]["boulder"+(index+1)]
var numTrys = widgetData[ "participants" ][partDel.thisIndex]["try"+(index+1)]
var resultList = []
if (resultString !== undefined) {
resultString = resultString.replace("t", "")
resultString = resultString.replace("z", "")
resultString = resultString.replace("b", "")
resultList = resultString.split(" ")
while (resultList.length < 2){
resultList.unshift(0)
}
}
else {
resultList = [-1,-1]
}
if (numTrys !== undefined) {
resultList.push(numTrys)
}
else {
resultList.push(-1)
}
return resultList
}
delegate: Item {
id: boulderResItm
anchors.verticalCenter: parent.verticalCenter
width: parent.width / ( boulderResRep.model )
height: parent.height
Canvas {
id: boulderResCv
property var resultData: boulderResRep.getDataForIcon(index)
onResultDataChanged: {
boulderResCv.requestPaint()
}
anchors.centerIn: parent
height: parent.height > parent.width ? parent.width * 0.9:parent.height * 0.9
width: height
onPaint: {
var width = 24//boulderResCv.width * 0.9
var height = width
var radius = width * 0.3
var offsetX = width * 0.05
var offsetY = height * 0.05
//console.log("drawing result rect with width: " + width + " and height: " + height)
var context = getContext("2d");
// clear all remainings from other routes
context.clearRect(0, 0, width, height);
context.beginPath();
context.moveTo(0 + offsetX + radius, 0 + offsetY);
// top line
context.lineTo(width - radius + offsetX, 0 + offsetY);
// top right corner
context.arc(width-radius + offsetX, radius + offsetY, radius, 1.5 * Math.PI, 0);
// right line
context.lineTo(width + offsetX, height - radius + offsetY);
// bottom right corner
context.arc(width-radius + offsetX, height - radius + offsetY, radius, 0, 0.5 * Math.PI);
// bottom line
context.lineTo(0 + radius + offsetX, height + offsetY);
// bottom left corner
context.arc(radius + offsetY, height - radius + offsetY, radius, 0.5 * Math.PI, Math.PI);
// left line
context.lineTo(0 + offsetX, radius + offsetY);
// top left corner
context.arc(radius + offsetX, radius + offsetY, radius, Math.PI, 1.5 * Math.PI);
// fill
if(resultData[0] !== -1 || resultData[2] !== -1) {
// if there is a result available -> draw background
context.fillStyle = "#b7b7b7";
}
else {
context.fillStyle = "transparent";
}
context.fill();
// outline
context.lineWidth = 1;
context.strokeStyle = Material.primaryTextColor;
context.stroke();
if(resultData[1] > 0){
// the first triangle
context.beginPath();
// top right corner
context.arc(width-radius + offsetX, radius + offsetY, radius, 1.75 * Math.PI, 0);
// right line
context.lineTo(width + offsetX, height - radius + offsetY);
// bottom right corner
context.arc(width-radius + offsetX, height - radius + offsetY, radius, 0, 0.5 * Math.PI);
// bottom line
context.lineTo(0 + radius + offsetX, height + offsetY);
// bottom left corner
context.arc(radius + offsetX, height - radius + offsetY, radius, 0.5 * Math.PI, 0.75 * Math.PI);
context.closePath();
context.fillStyle = "#44ed38";
context.fill();
// outline
context.lineWidth = 1;
context.strokeStyle = Material.primaryTextColor;
context.stroke();
if(resultData[0] > 0){
// the second triangle
context.beginPath();
// bottom left corner
context.arc(radius + offsetX, height - radius + offsetY, radius, 0.75 * Math.PI, 1 * Math.PI);
// left line
context.lineTo(0 + offsetX, radius + offsetY);
// top left corner
context.arc(radius + offsetX, radius + offsetY, radius, Math.PI, 1.5 * Math.PI);
// top line
context.lineTo(width - radius + offsetX, 0 + offsetY);
// top right corner
context.arc(width-radius + offsetX, radius + offsetY, radius, 1.5 * Math.PI, 1.75 * Math.PI);
context.closePath();
context.fillStyle = "#44ed38";
context.fill();
// outline
context.lineWidth = 1;
context.strokeStyle = Material.primaryTextColor;
context.stroke();
}
}
}
Label {
id: boulderResTrysLa
anchors.centerIn: parent
height: parent.height / 2
width: parent.width / 2
visible: !boulderResZoneLa.visible && boulderResCv.resultData[2] > 0
fontSizeMode: Text.Fit
font.pixelSize: height
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: "#dd000000"
text: boulderResCv.resultData[2]
}
Label {
id: boulderResZoneLa
anchors {
right: parent.right
bottom: parent.bottom
margins: boulderResCv.height * 0.05
}
height: parent.height / 2
width: parent.width / 2
visible: parseInt(text) > 0
fontSizeMode: Text.Fit
font.pixelSize: height
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: "#dd000000"
text: boulderResCv.resultData[1]
}
Label {
id: boulderResTopLa
anchors {
left: parent.left
top: parent.top
margins: boulderResCv.height * 0.05
}
height: parent.height / 2
width: parent.width / 2
visible: parseInt(text) > 0
fontSizeMode: Text.Fit
font.pixelSize: height
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: "#dd000000"
text: boulderResCv.resultData[0]
}
}
}
}
}

View file

@ -1,63 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.15
import QtQuick.Controls.Material.impl 2.15
ItemDelegate {
id: control
Material.background: "transparent"
background: Rectangle {
implicitHeight: control.Material.delegateHeight
color: control.highlighted ? control.Material.listHighlightColor : control.Material.background
Ripple {
width: parent.width
height: parent.height
clip: visible
pressed: control.pressed
anchor: control
active: control.down || control.visualFocus || control.hovered
color: control.Material.rippleColor
}
}
}

View file

@ -1,166 +0,0 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.12
import QtQuick.Templates 2.12 as T
import QtQuick.Controls.impl 2.12
import QtQuick.Controls.Material.impl 2.12
ColoredItemDelegate {
id: competitionDel
property bool over
property var thisData: control.widgetData["competitions"][index]
property string name: thisData["name"]
property string date: thisData["date_span"]
property var cats: thisData["cats"]
property int catId: thisData["cat_id"] === undefined ? 0:thisData["cat_id"]
property bool thisIsFavored: control.compFavorites.indexOf(parseInt(thisData['WetId'])) >= 0
property bool includedByFavorites: control.displayedCompCats.indexOf(-1) >= 0 && thisIsFavored
property bool includedByFilter: control.displayedCompCats.indexOf(parseInt(thisData['cat_id'])) >= 0
property bool thisIsVisible: includedByFavorites || includedByFilter
function updateVisibility() {
competitionDel.includedByFilter = control.displayedCompCats.indexOf(parseInt(competitionDel.thisData['cat_id'])) >= 0
competitionDel.thisIsFavored = control.compFavorites.indexOf(parseInt(thisData['WetId'])) >= 0
competitionDel.includedByFavorites = control.displayedCompCats.indexOf(-1) >= 0 && thisIsFavored
}
width: control.width
height: thisIsVisible ? compDelCol.height + 10 : 0
enabled: ((thisData["cats"] !== undefined && thisData["cats"].length > 0) || competitionDel.thisData["homepage"] !== undefined || getCompInfoUrls(index).length > 0) && height > 0
//visible: includedByFilter
opacity: 0
scale: 0.9
Connections {
target: control
function onDisplayedCompCatsChanged() {
competitionDel.updateVisibility()
}
function onCompFavoritesChanged() {
competitionDel.updateVisibility()
}
}
onThisDataChanged: {
if(thisIsVisible){
fadeInPa.start()
}
}
onThisIsVisibleChanged: {
if(thisIsVisible){
fadeInPa.start()
}
else {
fadeOutPa.start()
}
}
Behavior on height {
NumberAnimation {
duration: 400
}
}
onClicked: {
control.openComp(index)
}
ParallelAnimation {
id: fadeInPa
NumberAnimation { target: competitionDel; property: "opacity"; from: 0; to: 1.0; duration: 400 }
NumberAnimation { target: competitionDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 }
}
ParallelAnimation {
id: fadeOutPa
NumberAnimation { target: competitionDel; property: "opacity"; from: 1; to: 0; duration: 400 }
NumberAnimation { target: competitionDel; property: "scale"; from: 1; to: 0.8; duration: 400 }
}
Material.background: control.getCompCatData(catId) === undefined ? app.federalColor:control.getCompCatData(catId)["bgcolor"]
Column {
id: compDelCol
anchors.centerIn: parent
width: parent.width * 0.97
spacing: 10
RowLayout {
width: parent.width
Label {
id: nameLa
width: parent.width
Layout.fillWidth: true
font.bold: true
wrapMode: Text.WordWrap
text: name
}
ToolButton {
id: bookmarkTb
text: "\uf005"
font.family: competitionDel.thisIsFavored ? fa5solid.name : fa5regular.name
onClicked: {
control.editFavorites(!competitionDel.thisIsFavored, parseInt(thisData['WetId']))
}
Layout.alignment: Layout.Right
Behavior on font.family {
SequentialAnimation {
NumberAnimation {
property: "scale"
target: bookmarkTb
duration: 75
to: 0.8
}
NumberAnimation {
property: "scale"
target: bookmarkTb
duration: 75
to: 1
}
}
}
}
}
Label {
id: dateLa
text: date
}
}
Rectangle {
id: bottomLineRa
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
height: 1
color: "lightgrey"
}
}

View file

@ -53,17 +53,44 @@ ListView {
width: 8
visible: control.model > 0
visible: control.model.length > 0
active: true
}
header: Item {
id: topSpacerItm
width: parent.width
height: 10
}
footer: Item {
id: bottomSpacerItm
width: parent.width
height: 10
}
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
InfoArea {
id: infoArea
anchors {
left: control.left
right: control.right
top: control.top
margins: app.landscape() ? control.width * 0.4:control.width * 0.3
topMargin: control.height*( status === 901 ? 0.6:0.5) - height * 0.8
}
excludedCodes: [200, 902, 905]
errorCode: control.status
}
PullRefresher {
target: control

View file

@ -1,59 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.15
Dialog {
id: control
title: ""
property string content: ""
parent: Overlay.overlay
x: (parent.width - width) * 0.5
y: (parent.height - height) * 0.5
implicitWidth: parent.width * 0.9
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding
+ (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
+ (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
//width: app.width * 0.8
//height: implicitHeight
modal: true
standardButtons: Dialog.Close
header: Label {
text: control.title
visible: control.title
elide: Label.ElideRight
padding: 24
bottomPadding: 0
font.bold: true
font.pixelSize: 16
background: Rectangle {
radius: 2
color: control.Material.dialogColor
clip: true
}
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
contentItem: Label {
wrapMode: Text.Wrap
width: control.parent * 0.8
text: control.content
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
}

View file

@ -37,25 +37,47 @@ BusyIndicator {
anchors.fill: parent
RotationAnimation {
target: blueRockHoldIcon
property int currentHeight: 0
SequentialAnimation {
running: control.running
duration: control.animationSpeed
loops: Animation.Infinite
from: 0
to: 360
easing.type: Easing.InOutQuad
NumberAnimation {
target: item
property: "currentHeight"
from: 0
to: 800
duration: control.animationSpeed
}
}
Row {
Image {
id: blueRockHoldIcon
anchors.fill: parent
source: "qrc:/icons/blueRockHold.png"
mipmap: true
}
spacing: item.width / 9
Repeater {
id: repeater
model: 5
Rectangle {
property double heightMultiplier: Math.abs( Math.sin( ( ((item.currentHeight/100) + (index*(control.formFactor/repeater.model)))) * (Math.PI/8) ) )
anchors.verticalCenter: parent.verticalCenter
width: item.width / 9
height: ( heightMultiplier ) * ( item.height - 1 ) + 1
radius: width * 0.5
color: control.lineColor
}
}
}
}
}
}

View file

@ -19,20 +19,18 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtGraphicalEffects 1.0
import QtQuick.Controls.Material 2.12
import QtQuick.Controls.Material.impl 2.12
MouseArea {
ToolButton {
id: control
property string image
property color backgroundColor: "white"
property color textColor: "black"
property real imageScale: 1.3
property real imageScale: 1
property double glowRadius: 0.001
property double glowSpread: 0.2
property bool glowVisible: true
property double glowScale: 0.92
property double glowScale: 0.75
property double glowOpacity: 1
Behavior on backgroundColor {
@ -41,7 +39,7 @@ MouseArea {
}
}
Item {
contentItem: Item {
id: controlBackgroundContainer
anchors.fill: parent
@ -67,9 +65,9 @@ MouseArea {
anchors.fill: parent
radius: height * 0.2
radius: height * 0.5
color: Material.dialogColor //control.down ? Qt.darker(control.backgroundColor, 1.03) : control.backgroundColor
color: control.down ? Qt.darker(control.backgroundColor, 1.03) : control.backgroundColor
Image {
id: buttonIcon
@ -86,30 +84,22 @@ MouseArea {
scale: control.imageScale
}
Ripple {
id: ripple
visible: true
clipRadius: controlBackground.radius
clip: true
width: parent.width
height: parent.height
pressed: control.pressed
anchor: control
active: control.pressed || control.visualFocus || control.containsMouse
color: control.Material.rippleColor
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Item {
width: ripple.width
height: ripple.height
Rectangle {
anchors.fill: parent
radius: controlBackground.radius
}
}
Behavior on color {
ColorAnimation {
duration: 100
}
}
}
}
Text {
id: conetntText
text: qsTr(control.text)
anchors.centerIn: parent
font: parent.font
color: control.textColor
opacity: control.enabled ? 1:0.4
}
}

View file

@ -1,97 +0,0 @@
import QtQuick 2.10
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.15
Row {
id: multiGenResRow
property bool active: (parseInt(widgetData[ "route_order" ]) === -1) && (generalResRep.model > 0)
height: parent.height
width: active ? parent.width - resultLa.width:0
enabled: active
Repeater {
id: generalResRep
property var routes: getRoutes()
model: routes.length
function getRoutes() {
var obj = widgetData["route_names"]
var routes = []
for(var prop in obj) {
// go through the whole array and search for data keys
if (obj.hasOwnProperty(prop) && prop > -1) {
routes.push([prop, obj[prop]])
//console.log("found " + obj[prop] + " at index " + prop)
}
}
routes.sort(function(a, b) {
return a[0] - b[0];
});
return routes
}
delegate: Item {
id: boulderGenResItm
anchors.verticalCenter: parent.verticalCenter
width: parent.width / ( generalResRep.model )
height: parent.height
visible: multiGenResRow.active
Rectangle {
anchors {
left: parent.left
}
width: 1
height: parent.height
visible: index === 0
color: Material.primaryTextColor
}
Rectangle {
anchors {
right: parent.right
}
width: 1
height: parent.height
color: Material.primaryTextColor
}
Label {
id: boulderGenResLa
anchors.centerIn: parent
height: parent.height
width: parent.width * 0.9
fontSizeMode: Text.Fit
font.pixelSize: Math.abs( height * 0.6 )
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: widgetData["participants"][partDel.thisIndex]["result"+(generalResRep.routes[index][0])] === undefined ?
"":
widgetData[ "participants" ][partDel.thisIndex]["result"+(generalResRep.routes[index][0])]
}
}
}
}

View file

@ -1,90 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.12
Item {
id: control
property alias text: firstLabel.text
property alias font: firstLabel.font
property alias verticalAlignment: firstLabel.verticalAlignment
property int spacing: 30
property MovingLabel syncWithLabel
property int _spacing: syncWithLabel && syncWithLabel._labelWidth > _labelWidth ? (syncWithLabel._labelWidth - _labelWidth + syncWithLabel.spacing) : spacing
property alias _labelWidth: firstLabel.width
signal linkActivated(string link)
clip: true
height: firstLabel.height
onTextChanged: {
_resetScroll()
if(control.syncWithLabel)
control.syncWithLabel._resetScroll()
}
function startScroll(triggerSyncedLabel=true) {
if(control.syncWithLabel && triggerSyncedLabel)
control.syncWithLabel.startScroll(false)
if(control.width < firstLabel.width && !scrollAnimation.running)
scrollAnimation.start()
}
function _resetScroll() {
scrollAnimation.stop()
firstLabel.anchors.leftMargin = 0
}
Label {
id: firstLabel
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
onLinkActivated: control.onLinkActivated(link)
}
Label {
id: secondLabel
anchors {
left: firstLabel.right
leftMargin: control._spacing
verticalCenter: firstLabel.verticalCenter
}
visible: scrollAnimation.running
font: firstLabel.font
text: firstLabel.text
verticalAlignment: firstLabel.verticalAlignment
elide: firstLabel.elide
bottomPadding: firstLabel.bottomPadding
padding: firstLabel.padding
onLinkActivated: control.onLinkActivated(link)
}
MouseArea {
anchors.fill: parent
onClicked: control.startScroll()
}
NumberAnimation {
id: scrollAnimation
target: firstLabel
property: "anchors.leftMargin"
from: 0
to: -(firstLabel.width + control._spacing)
duration: (to / -100) * 1500
onRunningChanged: {
if(!running)
control._resetScroll()
}
}
}

View file

@ -18,7 +18,6 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.12
import QtGraphicalEffects 1.0
Item {
@ -33,10 +32,14 @@ Item {
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.7 // position of the item when refreshing
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.6 // position of the item when starting to refresh
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)
@ -58,7 +61,7 @@ Item {
anchors.fill: parent
radius: width * 0.5
color: Material.dialogColor
color: control.backgroundColor
}
}
property Component busyIndicator: BusyIndicator { running: true }
@ -104,7 +107,7 @@ Item {
ctx.reset()
ctx.lineWidth = lineWidth;
ctx.strokeStyle = control.Material.foreground;
ctx.strokeStyle = control.pullIndicatorColor;
// middle line
ctx.moveTo(width/2, topMargin);

View file

@ -1,158 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.15
ColoredItemDelegate {
id: partDel
property int thisIndex: index
property var thisData: widgetData[ "participants" ][partDel.thisIndex]
width: control.width
height: partDelCol.showSideBySide ? 40:70
text: ""
opacity: 0
scale: 0.9
onThisDataChanged: {
fadeInPa.start()
}
onClicked: {
app.openWidget({person:thisData["PerId"]})
}
highlighted: partDel.thisIndex % 2 == 0
ParallelAnimation {
id: fadeInPa
NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 }
NumberAnimation { target: partDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 }
}
Rectangle {
visible: widgetData["route_quota"] !== null && parseInt(widgetData["route_quota"]) - 1 === thisIndex
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.bottom
}
height: 3
color: Material.primaryTextColor
}
GridLayout {
id: partDelCol
property bool showSideBySide: app.landscape() || app.largeScreen()
anchors.fill: parent
anchors.margins: 5
columns: showSideBySide ? 2:1
rows: showSideBySide ? 1:2
columnSpacing: height * 0.2
rowSpacing: 0
Row {
id: partDelFirstRow
Layout.fillWidth: true
Layout.fillHeight: true
Label {
height: parent.height
width: text === "" ? parent.width * 0.08:parent.width * 0.1
fontSizeMode: Text.Fit
font.bold: true
font.pixelSize: Math.abs( partDelSecondRow.height > 0 ? height * 0.6:height * 0.4 )
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: partDel.thisData["result_rank"] === undefined ? "":partDel.thisData["result_rank"]
}
Label {
height: parent.height
width: parent.width * 0.5
fontSizeMode: Text.Fit
font.bold: true
font.pixelSize: Math.abs( partDelSecondRow.height > 0 ? height * 0.6:height * 0.4 )
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
text: partDel.thisData["firstname"] + " " + partDel.thisData["lastname"] + (partDel.thisData["start_number"] !== undefined ? (" (" + partDel.thisData["start_number"] + ")"):"")
}
Label {
height: parent.height
width: parent.width * 0.4
fontSizeMode: Text.Fit
font.bold: false
font.pixelSize: Math.abs( partDelSecondRow.height > 0 ? height * 0.4:height * 0.3 )
minimumPixelSize: height * 0.3 < 1 ? 1:height * 0.3
elide: "ElideRight"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: "<html>(<a href=\"" + (partDel.thisData["fed_url"] === undefined ? "":partDel.thisData["fed_url"]).toString() + "\">" + (widgetData[ "display_athlete" ] === "nation" ? partDel.thisData["nation"] : partDel.thisData["federation"]) + "</a>)</html>"
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
}
Row {
id: partDelSecondRow
Layout.preferredWidth: partDelCol.showSideBySide ? parent.width * 0.5 : parent.width
Layout.preferredHeight: partDelCol.showSideBySide ? parent.height : parent.height * 0.5
visible: boulderResRow.active || generalResRow.active || speedQualificationResRow.active || resultLa.active
BoulderResultRow {
id: boulderResRow
}
GeneralResultRow {
id: generalResRow
}
SpeedQualificationResultRow {
id: speedQualificationResRow
}
Label {
id: resultLa
property bool active: ( parseInt(widgetData[ "route_num_problems" ]) > 0 || widgetData["discipline"] !== "boulder" ) && parseInt(widgetData[ "route_order" ]) > -1 && !speedQualificationResRow.active
width: enabled ? parent.width * 0.25:0
height: enabled ? parent.height:0
enabled: active
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
fontSizeMode: Text.Fit
font.pixelSize: Math.abs( height * 0.6 )
minimumPixelSize: 1
text: widgetData[ "participants" ][partDel.thisIndex]["result"] === undefined ? "":widgetData[ "participants" ][partDel.thisIndex]["result"]
}
}
}
}

View file

@ -1,5 +1,5 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.3
Dialog {
@ -7,9 +7,9 @@ Dialog {
property var dataObj
property string subTitle: ""
property int implicitY: parent.height - implicitHeight
signal selectionFinished(int index, var data)
signal linkActivated(string link)
parent: Overlay.overlay
@ -33,35 +33,38 @@ Dialog {
id: selectorPuHeaderCol
width: control.width
height: headerLa.height + headerTopSpacerItm.height + (control.subTitle ? headerSubLa.height:0)
height: headerSubLa.text !== "" && headerLa.text !== "" ? 73 : 40
Item {
id: headerTopSpacerItm
height: control.padding
width: parent.width
}
MovingLabel {
Label {
id: headerLa
anchors.horizontalCenter: parent.horizontalCenter
width: selectorPuHeaderCol.width - control.padding * 2
visible: control.title
width: selectorPuHeaderCol.width
elide: "ElideRight"
padding: control.padding
bottomPadding: 0
font.bold: true
font.pixelSize: 16
text: control.title
onLinkActivated: control.linkActivated(link)
onLinkActivated: {
console.log("Opening " + link)
Qt.openUrlExternally(link)
}
}
Label {
id: headerSubLa
anchors.horizontalCenter: parent.horizontalCenter
width: selectorPuHeaderCol.width - control.padding * 2
visible: control.subTitle
wrapMode: Text.Wrap
width: selectorPuHeaderCol.width
elide: "ElideRight"
padding: control.padding
topPadding: 5
bottomPadding: 0
font.bold: true
@ -70,7 +73,8 @@ Dialog {
text: control.subTitle
onLinkActivated: {
control.linkActivated(link)
console.log("Opening " + link)
Qt.openUrlExternally(link)
}
}
@ -79,7 +83,6 @@ Dialog {
background: Item {
Rectangle {
id: backgroundRect
anchors {
fill: parent
bottomMargin: -radius

View file

@ -1,124 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QZXing 3.3
import QtGraphicalEffects 1.0
Dialog {
id: control
property string _shareUrl
property string _compName
parent: Overlay.overlay
x: (parent.width - width) * 0.5
y: (parent.height - height) * 0.5
modal: true
//% "Share these results"
title: qsTrId("#shareResultsHeadline")
onClosed: {
shareComponentLoader.sourceComponent = null
}
contentItem: Loader {
id: shareComponentLoader
asynchronous: false
sourceComponent: null
}
Component {
id: shareComponent
StackLayout {
id: stackLayout
currentIndex: 0
RowLayout {
id: menuRow
Repeater {
id: buttonRepeater
property var buttons: [
//% "Link"
["\uf0c1", qsTrId("#shareByLink"), serverConn.shareResultsAsUrl],
//% "QR-Code"
["\uf029", qsTrId("#shareByQrCode"), function() {
stackLayout.currentIndex = 1
}
],
//% "Poster"
["\uf1c1", qsTrId("#shareByPoster"), serverConn.shareResultsAsPoster],
]
model: buttons
delegate: Button {
flat: true
font.family: fa5solid.name
text: "<font size=\"+4\">" + modelData[0] + "</font><br><br> " + modelData[1] + " "
onClicked: buttonRepeater.buttons[index][2](_shareUrl, _compName)
}
}
}
Image {
id: qrCodeImage
property int finalSize: app.landscape() ? app.height * 0.8 : app.width * 0.8
property int size: stackLayout.currentIndex === 1 ? finalSize:menuRow.height
Layout.preferredHeight: size
Layout.preferredWidth: size
sourceSize.width: finalSize
sourceSize.height: finalSize
fillMode: Image.PreserveAspectFit
source: "image://QZXing/encode/" + _shareUrl + "?border=true&correctionLevel=H"
RectangularGlow {
id: effect
anchors.fill: blurRockLogoBackgroundRect
glowRadius: 0
spread: 0
opacity: 0.8
color: "black"
cornerRadius: blurRockLogoBackgroundRect.radius
}
Rectangle {
id: blurRockLogoBackgroundRect
anchors.centerIn: parent
width: parent.width * 0.25
height: width
radius: height * 0.2
color: "white"
Image {
anchors.centerIn: parent
width: parent.width * 0.8
height: width
mipmap: true
source: "qrc:/icons/blueRockHold.png"
}
}
Behavior on size {
NumberAnimation {
duration: 200
}
}
}
}
}
function appear(shareUrl, compName) {
_shareUrl = shareUrl
_compName = compName
shareComponentLoader.sourceComponent = shareComponent
control.open()
}
}

View file

@ -1,289 +0,0 @@
.pragma library
/**
* Function to extract results from PartiipantFromApi
* @param {ParticipantFromApi} fromApi
* @return {Result[]}
*/
function _extractResults(fromApi) {
const results = Array(7);
const existingResults = Object.keys(fromApi).filter(key =>
key.match(/result[0-9]/),
);
for (const result of existingResults) {
let roundNumber = 0;
const match = result.match(/result([0-9])/);
if (match !== undefined && match !== null) {
roundNumber = parseInt(match[1]);
}
if (roundNumber < 0) {
continue;
}
results[roundNumber] = {
rank: parseInt(fromApi[`result_rank${roundNumber}`] as string),
result: fromApi[`result${roundNumber}`] as string,
};
}
return results;
}
/**
* Function to clean up participants from the api
* @param {ParticipantFromApi} fromApi
* @return {Participant}
*/
function participantFromApiParticipant(
fromApi,
) {
const results = _extractResults(fromApi);
return {
id: fromApi.PerId,
firstName: fromApi.firstname,
lastName: fromApi.lastname,
results: results,
overallRank: fromApi.result_rank,
startNumber: parseInt(fromApi.start_number),
};
}
/**
*
* @param {number} roundNumber
* @param {RouteNames} routeNames
* @return {string | undefined}
*/
function getRoundName(
roundNumber,
routeNames,
) {
if (roundNumber < 2 || roundNumber > 6) return undefined;
return routeNames[roundNumber];
}
/**
*
* @param {string} name
* @return {number}
*/
function getRoundRank(name) {
const match = name.match(/1\/([842])/);
if (match === undefined || match === null || match.length !== 2) {
return 2;
}
return parseInt(match[1]);
}
/**
*
* @param {SpeedRoundPair} pair
* @param {number} roundIndex
*/
function computeWinnerOfPair(pair, roundIndex) {
if (
!(pair.laneA && pair.laneA.participant.results[roundIndex] && pair.laneA.participant.results[roundIndex].rank) ||
!(pair.laneB && pair.laneB.participant.results[roundIndex] && pair.laneB.participant.results[roundIndex].rank)
)
return;
pair.laneA.result = pair.laneA.participant.results[roundIndex];
pair.laneB.result = pair.laneB.participant.results[roundIndex];
if (pair.winner === undefined) {
pair.winner =
pair.laneA.participant.results[roundIndex].rank >
pair.laneB.participant.results[roundIndex].rank
? 'B'
: 'A';
}
}
/**
*
* @param {SpeedRoundPair} pair
* @param {number} roundNumber
* @return {Participant | undefined}
*/
function getWinnerOfPair(
pair,
roundNumber,
) {
computeWinnerOfPair(pair, roundNumber);
return {
['A']: pair.laneA ? pair.laneA.participant : undefined,
['B']: pair.laneB ? pair.laneB.participant : undefined,
['']: undefined,
}[pair.winner ? pair.winner:''];
}
/**
*
* @param {SpeedRoundPair} pair
* @param {number} roundNumber
* @return {Participant | undefined}
*/
function getLooserOfPair(
pair,
roundNumber,
) {
computeWinnerOfPair(pair, roundNumber);
return {
['A']: pair.laneB ? pair.laneB.participant : undefined,
['B']: pair.laneA ? pair.laneA.participant : undefined,
['']: undefined,
}[pair.winner ? pair.winner:''];
}
/**
*
* @param {number} roundIndex index of the new round
* @param {string} roundName name of the new round
* @param {SpeedRound} previousRound
* @param {number} roundRank
* @param {boolean} takeLooser
* @return {SpeedRound}
*/
function computeRoundFromPreviousRound(
roundIndex,
roundName,
previousRound,
roundRank,
takeLooser = false,
) {
const getAdvancingParticipant = takeLooser
? getLooserOfPair
: getWinnerOfPair;
const nextRoundPairs = new Array(roundRank / 2).fill(0).map((_, i) => {
const laneAParticipant = getAdvancingParticipant(
previousRound.pairs[i * 2],
previousRound.roundIndex,
);
const laneBParticipant = getAdvancingParticipant(
previousRound.pairs[i * 2 + 1],
previousRound.roundIndex,
);
return {
laneA:
laneAParticipant === undefined
? undefined
: {
participant: laneAParticipant,
},
laneB:
laneBParticipant === undefined
? undefined
: {
participant: laneBParticipant,
},
};
});
return {
pairs: nextRoundPairs,
roundIndex: roundIndex,
roundName: roundName,
};
}
/**
*
* @param {SpeedCompetitionCategoryResult} result The result to process
* @return {SpeedFlowchartResult}
*/
function convertResultsToSpeedFlowchartResult(
result,
) {
const rounds = [];
const convertedParticipants = result.participants
.map(fromApi => participantFromApiParticipant(fromApi))
// sort by qualification result
.sort((a, b) => a.results[0].rank - b.results[0].rank);
const roundIndices = Object.keys(result.route_names)
.map(number => parseInt(number))
.filter(number => number > 0);
// process first round
const firstRoundName = getRoundName(roundIndices[0], result.route_names);
const tmpRoundName = getRoundName(roundIndices[0], result.route_names);
const firstRoundRank = getRoundRank(
tmpRoundName ? tmpRoundName:'',
);
const getOpponent = (ofRank) => {
return convertedParticipants[firstRoundRank * 2 - 1 - ofRank];
};
// Should be:
// 0, 1, 2, 3, 4, 5, 6, 7
// - 1,16, 8, 9, 4,13, 5,12, 2,15, 7,10, 3,14, 6,11
// - 1, 8, 4, 5, 2, 7, 3, 6 for firstRoundNumber=8
// - 1, 4, 2, 3 for firstRoundNumber=4
// - 1, 2 for firstRoundNumber=2
// TODO: come up with a proper alogorithm maybe
const ranksOfLaneAInOrder = [
[1, 2],
[1, 4, 2, 3],
[1, 8, 4, 5, 2, 7, 3, 6],
][Math.floor(firstRoundRank / 4)];
const firstRoundPairs = ranksOfLaneAInOrder.map(rank => {
return {
laneA: { participant: convertedParticipants[rank - 1] },
laneB: { participant: getOpponent(rank - 1) },
};
});
const firstRound = {
pairs: firstRoundPairs,
roundIndex: roundIndices[0],
roundName: firstRoundName,
};
rounds.push(firstRound);
// compute following rounds
let roundIndex = roundIndices[1];
for (let roundRank = firstRoundRank; roundRank > 2; roundRank /= 2) {
rounds.push(
computeRoundFromPreviousRound(
roundIndex,
result.route_names[roundIndex] ? result.route_names[roundIndex]:'',
rounds[rounds.length - 1],
roundRank,
),
);
roundIndex++;
}
// compute final and semi final
const semifinalRoundIndex = roundIndex++;
const semifinal = computeRoundFromPreviousRound(
semifinalRoundIndex,
result.route_names[semifinalRoundIndex] ? result.route_names[semifinalRoundIndex]:'',
rounds[rounds.length - 1],
2,
true,
);
computeWinnerOfPair(semifinal.pairs[0], semifinalRoundIndex);
rounds.push(semifinal);
const finalRoundIndex = roundIndex;
const final = computeRoundFromPreviousRound(
finalRoundIndex,
result.route_names[finalRoundIndex] ? result.route_names[finalRoundIndex]:'',
rounds[rounds.length - 2],
2,
);
computeWinnerOfPair(final.pairs[0], finalRoundIndex);
rounds.push(final);
return {
rounds: rounds,
};
}

View file

@ -20,324 +20,390 @@ import QtQuick 2.10
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.1
import QtGraphicalEffects 1.0
import "SpeedFlowChart.js" as SpeedFlowChart
Item {
ListView {
id: control
property var flowchartData
property var allFlowchartData
property int rounds: 0
property int tileSize: app.height / 8 * 0.8
property int refreshes: 0
property int roundRefreshes: 1
anchors.fill: parent
anchors.margins: 10
spacing: app.width * 0.1
orientation: ListView.LeftToRight
boundsBehavior: ListView.StopAtBounds
onFlowchartDataChanged: {
prepareData()
}
model: 0
function prepareData() {
if(!control.enabled || control.flowchartData === undefined || control.flowchartData['route_names'] === undefined)
return
/*refreshes += 1
var flowchartResult = SpeedFlowChart.convertResultsToSpeedFlowchartResult(control.flowchartData)
const l = flowchartResult.rounds.length;
const dummy = { dummy: true };
flowchartResult.rounds[l - 2].pairs = [
dummy,
...flowchartResult.rounds[l - 1].pairs,
...flowchartResult.rounds[l - 2].pairs,
];
flowchartResult.rounds[l - 2].roundName = `${
flowchartResult.rounds[l - 1].roundName
} / ${flowchartResult.rounds[l - 2].roundName}`;
flowchartResult.rounds.pop();
control.allFlowchartData = flowchartResult
}
ListView {
id: roundListView
property int columnWidth: height * 0.4
property int columnHeight: height
anchors {
top: parent.top
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
if(refreshes > 2){
roundRefreshes += 1
}
width: Math.min((columnWidth + spacing) * model, control.width)
console.log("refreshes: " + refreshes + " rounds: " + roundRefreshes)
spacing: app.height * 0.05
// create competition like data (just testing)
for(var part in flowchartData['participants']){
if(flowchartData['participants'].hasOwnProperty(part)){
orientation: ListView.LeftToRight
boundsBehavior: ListView.StopAtBounds
model: control.allFlowchartData.rounds.length
delegate: Item {
id: roundItem
property int thisIndex: index
property var thisData: control.allFlowchartData.rounds[thisIndex]
property bool thisRoundIsValid: true
property bool thisIsLastRound: thisIndex === roundListView.model - 1
property bool thisIsSemiFinal: thisIndex === roundListView.model - 2
property int tileSize: (roundItem.height / 8 - roundNameLa.height) * 1.45
width: roundListView.columnWidth
height: roundListView.columnHeight
Column {
id: roundCol
anchors.fill: parent
Label {
id: roundNameLa
width: parent.width
height: control.height * 0.05
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
fontSizeMode: Text.Fit
font.pixelSize: height * 0.6
minimumPixelSize: 1
font.bold: true
text: roundItem.thisData.roundName ?
roundItem.thisData.roundName :
"-"
for(var r = 2 + roundRefreshes; r < 7; r++){
delete flowchartData['participants'][part]["result"+r]
delete flowchartData['participants'][part]["result_rank"+r]
}
Repeater {
id: rectRep
model: roundItem.thisData.pairs.length
if(parseInt(flowchartData['participants'][part]["result_rank0"]) > 14 + refreshes) {
delete flowchartData['participants'][part]["result_rank2"]
}
}
}
delegate: Item {
id: matchItm
property bool lowerPart: (index%2 > 0)
property var thisData: roundItem.thisData.pairs[index]
property int thisIndex: index
delete flowchartData['route_names'][2]
delete flowchartData['route_names'][3]
delete flowchartData['route_names'][4]
delete flowchartData['route_names'][5]
delete flowchartData['route_names'][6]
*/
height: (roundItem.height - roundNameLa.height) / rectRep.model - roundCol.spacing
width: roundItem.width
//flowchartData['route_names'] = flowchartData['route_names'].slice(0,)
onThisDataChanged: {
fadeInPa.start()
// array to store the restructured data
var allData = []
control.allFlowchartData = []
control.rounds = Object.keys(control.flowchartData['route_names']).length > 2 ? control.flowchartData['route_names']["2"].includes("8") ? 2:1 : 0
//console.log(JSON.stringify(flowchartData['route_names']))
for(var round in flowchartData['route_names']){
if(flowchartData['route_names'].hasOwnProperty(round) && parseInt(round) >= 0){
//console.log(round)
if(parseInt(round) === 0){
// this is the first round
// find pairs (always wors vs. best (1-16; 1-15; ...)) (they are sorted by the rank of the better athlete (1-2-3-4-5-6-7-8)
var qualificationResults = []
for(var x = 0; x < flowchartData['participants'].length; x++){
qualificationResults.push(flowchartData['participants'][x])
}
qualificationResults.sort(function(a, b) {
return parseInt(a["result_rank0"]) - parseInt(b["result_rank0"]);
});
var nextRoundPairs = []
var totalMatches = (parseInt(Object.keys(control.flowchartData['route_names']).length > 2 ? control.flowchartData['route_names']["2"].includes("8") ? 2:1 : 0) + 2)
var nextRoundMatches = Math.pow(2, totalMatches-1)
for(var i = 0; i < nextRoundMatches; i++){
nextRoundPairs.push([qualificationResults[i], qualificationResults[nextRoundMatches*2-i-1]])
}
// build second round pairs (sorted by the rank of the better athlete and worst vs. best (1-8; 2-7; ... ))
var sortedFirstRoundPairs = []
for(i = 0; i < nextRoundMatches; i += 1){
sortedFirstRoundPairs.push([nextRoundPairs.shift(), nextRoundPairs.pop()])
}
// sort these pairs (containing two pairs of athletes) by the rank of the better athlete (1-4;2-3)
var finalSortedFirstRoundPairs = []
for(i=0; i < nextRoundMatches/4; i++){
finalSortedFirstRoundPairs.push(sortedFirstRoundPairs[i])
finalSortedFirstRoundPairs.push(sortedFirstRoundPairs[nextRoundMatches/2-i-1])
}
// convert the list of pairs of pairs of athletes back to a single list of pairs of athletes
var finalFirstRoundPairs = []
for(i=0; i < finalSortedFirstRoundPairs.length; i++){
finalFirstRoundPairs.push(finalSortedFirstRoundPairs[i][0])
finalFirstRoundPairs.push(finalSortedFirstRoundPairs[i][1])
}
// push the first round to all data
finalFirstRoundPairs.push(2)
finalFirstRoundPairs.push(flowchartData['route_names'][2])
allData.push(finalFirstRoundPairs)
}
else if(parseInt(round) > 0 ){
// this is not the first round
var nextRound = []
// only used when the current round is the 1/2 final
var smallFinal = []
var Final = []
for(var i = 0; i < allData[allData.length-1].length-2; i+=1){
var thisPair = allData[allData.length-1][i]
var thisWinner
var thisLooser
var thisWinnerIsFirstOfNewPair = i%2 === 0
if(thisPair[0] === undefined || thisPair[1] === undefined){
continue
}
ParallelAnimation {
id: fadeInPa
NumberAnimation { target: matchItm; property: "opacity"; from: 0; to: 1.0; duration: 400 }
NumberAnimation { target: matchItm; property: "scale"; from: 0.8; to: 1.0; duration: 400 }
if(thisWinnerIsFirstOfNewPair){
nextRound.push([])
}
Canvas {
id: lineCanvas
width: app.width; height: app.height
contextType: "2d"
//thisPair[0]["result_rank"] = thisPair[0]["result_rank"+round]
//thisPair[1]["result_rank"] = thisPair[1]["result_rank"+round]
visible: !roundItem.thisIsLastRound
property int targetYFactor: matchItm.lowerPart ? -1:1
Path {
id: firstPartPath
startX: matchItm.width
startY: matchItm.height * 0.5
PathQuad {
id: firstPartPathQuad
relativeX: roundListView.spacing * 0.5
relativeY: matchItm.height * 0.25 * lineCanvas.targetYFactor
relativeControlX: relativeX
relativeControlY: 0
}
PathQuad {
relativeX: roundListView.spacing * 0.5
relativeY: matchItm.height * 0.25 * lineCanvas.targetYFactor
relativeControlX: 0
relativeControlY: relativeY
}
}
onPaint: {
context.strokeStyle = "lightgrey"
context.lineWidth = matchRect.height * 0.05
context.path = firstPartPath;
context.stroke();
}
if(parseInt(thisPair[0]["result_rank"+round]) < parseInt(thisPair[1]["result_rank"+round])){
thisWinner = thisPair[0]
thisLooser = thisPair[1]
}
else if(parseInt(thisPair[0]["result_rank"+round]) > parseInt(thisPair[1]["result_rank"+round])) {
thisWinner = thisPair[1]
thisLooser = thisPair[0]
}
else {
// no result yet!!
//console.log("got no winner yet, rank 0: " + thisPair[0]["result_rank"+round] + " rank 1: " + thisPair[1]["result_rank"+round])
continue
}
RectangularGlow {
id: effect
visible: matchRect.visible
anchors.fill: matchRect
glowRadius: 0
spread: 0
opacity: 0.3
color: "black"
cornerRadius: matchRect.radius
//console.log(thisWinner['firstname']+" has won in round " + round)
if(round - control.rounds === 2){
// if we are in the 1/2 final
Final.push(thisWinner)
smallFinal.push(thisLooser)
}
Rectangle {
id: matchRect
anchors {
centerIn: !matchItm.thisIsFinal ? parent:undefined
bottom: matchItm.thisIsFinal ? parent.bottom:undefined
}
visible: !matchItm.thisData.dummy
width: parent.width
height: roundItem.tileSize
color: Material.dialogColor
radius: height * 0.2
Column {
spacing: 0
anchors.fill: parent
anchors.margins: matchRect.radius * 0.5
Repeater {
// for the two athletes
model: 2
delegate: RowLayout {
id: laneRow
property var thisData: index === 0 ? matchItm.thisData.laneA : matchItm.thisData.laneB
property var participant: thisData ? thisData.participant : undefined
property var result: thisData ? thisData.result : undefined
property var lane: index === 0 ? "A":"B"
property bool isWinner: matchItm.thisData.winner === laneRow.lane
height: parent.height / 2 - parent.spacing
width: parent.width
spacing: height * 0.1
Label {
Layout.preferredHeight: parent.height
Layout.preferredWidth: parent.width * 0.08
height: parent.height
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.pixelSize: height * 0.4
font.bold: true
opacity: 0.7
text: laneRow.participant ?
(
laneRow.participant.results[0].rank < 10 ?
laneRow.participant.results[0].rank + " ":
laneRow.participant.results[0].rank
):
""
}
Label {
Layout.preferredHeight: parent.height
Layout.maximumWidth: parent.width * 0.6
height: parent.height
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
font.pixelSize: height * 0.5
font.bold: laneRow.isWinner
elide: "ElideRight"
color: laneRow.isWinner ? Material.color(Material.Green):Material.primaryTextColor
text: laneRow.participant ? laneRow.participant.firstName + " " + laneRow.participant.lastName :"-"
}
Rectangle {
Layout.preferredHeight: parent.height * 0.8
Layout.preferredWidth: parent.width * 0.13
visible: laneRow.participant && laneRow.participant.startNumber ? true:false
radius: height / 2
border.width: parent.height * 0.03
border.color: Material.frameColor
color: "transparent"
Label {
anchors.fill: parent
padding: parent.parent.height * 0.1
leftPadding: padding * 2
rightPadding: leftPadding
font.pixelSize: parent.height * 0.5
font.bold: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: laneRow.participant ? laneRow.participant.startNumber:""
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
}
Label {
Layout.preferredHeight: parent.height
Layout.preferredWidth: parent.width * 0.15
height: parent.height
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignRight
font.pixelSize: height * 0.5
font.bold: laneRow.isWinner
color: laneRow.isWinner ? Material.color(Material.Green):Material.primaryTextColor
text: laneRow.result ? laneRow.result.result : ""
}
}
}
}
else {
nextRound[nextRound.length-1].push(thisWinner)
}
}
if(smallFinal.length > 0 && Final.length > 0){
// Final
allData.push([Final, parseInt(round)+2, flowchartData['route_names'][String(parseInt(round)+2)] + " / " + flowchartData['route_names'][String(parseInt(round)+1)] ])
// small Final
allData.push([smallFinal, parseInt(round)+1])
//break
}
else {
nextRound.push(parseInt(round) + 1 )
nextRound.push(flowchartData['route_names'][parseInt(round) + 1])
allData.push(nextRound)
}
}
}
}
control.allFlowchartData = allData
control.model = (parseInt(Object.keys(control.flowchartData['route_names']).length > 2 ? control.flowchartData['route_names']["2"].includes("8") ? 2:1 : 0) + 2)
//console.log(JSON.stringify(allData))
}
delegate: Column {
id: roundCol
property int thisIndex: index
property int thisRound: thisRoundIsValid ? control.allFlowchartData[roundCol.thisIndex][control.allFlowchartData[roundCol.thisIndex].length-2]:-1
property bool thisRoundIsValid: control.allFlowchartData !== undefined && control.allFlowchartData[roundCol.thisIndex] !== undefined && control.allFlowchartData[roundCol.thisIndex].length > 2
property bool thisIsLastRound: thisIndex === control.model - 1
width: app.width * 0.5
height: control.height
//spacing: app.width * 0.1
Label {
id: roundNameLa
width: parent.width
height: control.height * 0.05
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
fontSizeMode: Text.Fit
font.pixelSize: height * 0.6
minimumPixelSize: 1
font.bold: true
text: roundCol.thisRoundIsValid && control.allFlowchartData[roundCol.thisIndex][control.allFlowchartData[roundCol.thisIndex].length-1] !== undefined ? control.allFlowchartData[roundCol.thisIndex][control.allFlowchartData[roundCol.thisIndex].length-1] : "-"
}
Repeater {
id: rectRep
model: Math.max( Math.pow(2, control.model-1) * Math.pow(0.5, (index)), 2)
delegate: Item {
id: matchItm
property bool lowerPart: (index%2 > 0)
property var matchData: roundCol.thisRoundIsValid ? control.allFlowchartData[ thisIsSmallFinal ? roundCol.thisIndex+1 : roundCol.thisIndex][ thisIsSmallFinal ? 0:matchItm.thisIndex]:undefined
property int thisIndex: index
property int thisRound: parseInt(roundCol.thisRound) - (thisIsSmallFinal ? 1:0)
property var thisMatchData: thisMatchDataIsValid ? matchData:[]
property bool thisMatchDataIsValid: (matchData !== undefined && matchData !== null && typeof matchData === "object" && matchData.length > 0)
property bool thisMatchIsOver: thisMatchDataIsValid && thisMatchData[0]['result_rank'+thisRound] !== undefined && thisMatchData[1]['result_rank'+thisRound] !== undefined
property bool thisIsFinal: roundCol.thisIsLastRound && thisIndex === rectRep.model - 2
property bool thisIsSmallFinal: roundCol.thisIsLastRound && thisIndex === rectRep.model - 1
property int winnerIndex: thisMatchIsOver ? (parseInt(thisMatchData[0]['result_rank'+thisRound]) < parseInt(thisMatchData[1]['result_rank'+thisRound]) ? 0:1) : -1
height: !roundCol.thisIsLastRound ? (roundCol.height - roundNameLa.height) / rectRep.model - roundCol.spacing : (thisIsFinal ? (roundCol.height - roundNameLa.height) * 0.5 + control.tileSize * 0.5 : (roundCol.height - roundNameLa.height) - (roundCol.height - roundNameLa.height) * 0.5 + control.tileSize * 0.5 )
width: parent.width
onMatchDataChanged: {
fadeInPa.start()
}
ParallelAnimation {
id: fadeInPa
NumberAnimation { target: matchItm; property: "opacity"; from: 0; to: 1.0; duration: 400 }
NumberAnimation { target: matchItm; property: "scale"; from: 0.8; to: 1.0; duration: 400 }
}
Canvas {
id: lineCanvas
width: app.width; height: app.height
contextType: "2d"
visible: !roundCol.thisIsLastRound
property int targetX: matchItm.width + control.spacing
property int targetY: matchItm.lowerPart ? 0:matchItm.height
Path {
id: myPath
startX: matchRect.x + matchRect.width * matchRect.scale ; startY: matchRect.y + matchRect.height * 0.5
PathCurve { x: lineCanvas.targetX ; y: lineCanvas.targetY }
}
onPaint: {
context.strokeStyle = Qt.rgba(.4,.6,.8);
context.path = myPath;
context.stroke();
}
}
}
Rectangle {
id: matchRect
Loader {
id: blueRockBadgeLoader
anchors {
centerIn: !matchItm.thisIsFinal ? parent:undefined
bottom: matchItm.thisIsFinal ? parent.bottom:undefined
}
x: (parent.width - width) / 2
y: (parent.height - roundNameLa.height - height) / 2 + roundNameLa.height
//anchors.verticalCenterOffset: matchItm.lowerPart ? 10:10
width: parent.width
height: control.tileSize
//scale: 0.9
color: "white"
border.color: "lightgrey"
border.width: 1
radius: height * 0.2
Material.elevation: 10
sourceComponent: roundItem.thisIsSemiFinal ? blueRockBadgeComponent:undefined
Grid {
columns: app.landscape() ? 2:0
rows: app.landscape() ? 0:2
Component {
id: blueRockBadgeComponent
spacing: app.landscape() ? height * 0.4:0
BlueRockBadge {
width: roundItem.width * 0.8
height: width * 0.25
anchors.fill: parent
anchors.margins: matchRect.radius * 0.5
Repeater {
// for the two athletes
model: 2
delegate: RowLayout {
height: app.landscape() ? parent.height:parent.height / 2 - parent.spacing
width: app.landscape() ? parent.width / 2 - parent.spacing : parent.width
spacing: app.landscape() ? height * 0.1:height * 0.1
Label {
Layout.preferredHeight: parent.height
height: parent.height
verticalAlignment: Text.AlignVCenter
font.pixelSize: height * 0.7
fontSizeMode: Text.Fit
minimumPixelSize: 1
Layout.alignment: Layout.Left
text: matchItm.thisMatchData[index] !== undefined ? (parseInt(matchItm.thisMatchData[index]['result_rank0']) < 10 ?matchItm.thisMatchData[index]['result_rank0'] + " ":matchItm.thisMatchData[index]['result_rank0']): ""
}
Label {
Layout.preferredHeight: parent.height
height: parent.height
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
//horizontalAlignment: app.landscape() ? Text.AlignLeft : Text.AlignLeft
font.pixelSize: app.landscape() ? height * 0.4 : height * 0.6
minimumPixelSize: (app.landscape() ? height * 0.35 : height * 0.5) < 1 ? 1 : (app.landscape() ? height * 0.35 : height * 0.5)
fontSizeMode: Text.Fit
font.bold: matchItm.winnerIndex === index
elide: "ElideRight"
Layout.fillWidth: true
color: matchItm.winnerIndex === index ? "green":"black"
text: matchItm.thisMatchData[index] !== undefined ? matchItm.thisMatchData[index]['firstname'] + " " + matchItm.thisMatchData[index]['lastname'] :"-"
}
Label {
Layout.preferredHeight: parent.height
height: parent.height
verticalAlignment: Text.AlignVCenter
font.pixelSize: app.landscape() ? height * 0.35 : height * 0.5
Layout.alignment: Layout.Right
text: matchItm.thisMatchData[index] !== undefined && matchItm.thisMatchData[index]['result'+matchItm.thisRound] !== undefined ?
( parseFloat(matchItm.thisMatchData[index]['result'+matchItm.thisRound]) ?
(parseFloat(matchItm.thisMatchData[index]['result'+matchItm.thisRound]).toFixed(2))
: matchItm.thisMatchData[index]['result'+matchItm.thisRound] )
: "-"
}
}
}
}
}
}

View file

@ -1,131 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.12
import QtQuick.Layouts 1.15
import QtPurchasing 1.12
Page {
id: control
anchors.fill: parent
Connections {
target: speedFlowChartProduct
function onPurchaseFailed() {
//% "Purchase failed"
purchaseBt.text = qsTrId("#purchaseFailed")
purchaseBt.enabled = false
buttonTextResetTimer.start()
}
}
Timer {
id: buttonTextResetTimer
interval: 2000
running: false
repeat: false
onTriggered: {
purchaseBt.text = (speedFlowChartProduct.status === Product.Registered
//% "Buy now for"
? qsTrId("#buyNowFor") + " " + speedFlowChartProduct.price
//% "This item is currently unavailable"
: qsTrId("#itemIsUnavailable"))
purchaseBt.enabled = true
}
}
Column {
id: lockedLayout
anchors {
fill: parent
topMargin: parent.height * 0.02
bottomMargin: parent.height * 0.05
rightMargin: parent.width * 0.1 + 20
leftMargin: parent.width * 0.1 + 20
}
spacing: lockedLayout.height * 0.01
Label {
anchors.horizontalCenter: parent.horizontalCenter
height: lockedLayout.height * 0.03
width: lockedLayout.width
//% "This is a premium feature."
text: qsTrId("#thisIsAPremiumFeature")
font.pixelSize: height
font.bold: true
fontSizeMode: Text.Fit
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
minimumPixelSize: 1
}
SwipeGallery {
property string platformIcons: Qt.platform.os === "osx" || Qt.platform.os === "iso" ? "ios":"android"
height: lockedLayout.height * 0.85
width: lockedLayout.width
images: [
"qrc:/screenshots/SpeedFlowchartDemo/" + platformIcons + "/1.jpeg",
"qrc:/screenshots/SpeedFlowchartDemo/" + platformIcons + "/2.jpeg",
"qrc:/screenshots/SpeedFlowchartDemo/" + platformIcons + "/3.jpeg"
]
}
Button {
id: purchaseBt
anchors.horizontalCenter: parent.horizontalCenter
height: lockedLayout.height * 0.07
enabled: speedFlowChartProduct.status === Product.Registered
text: speedFlowChartProduct.status === Product.Registered
? "\uf218 "+ qsTrId("#buyNowFor") + " " + speedFlowChartProduct.price
: qsTrId("#itemIsUnavailable")
font.family: fa5solid.name
font.pixelSize: height * 0.4
font.capitalization: Font.MixedCase
onClicked: speedFlowChartProduct.purchase()
}
RowLayout {
id: bottomRow
anchors.horizontalCenter: parent.horizontalCenter
height: lockedLayout.height * 0.065
Button {
id: restorePurchaseButton
Layout.alignment: Layout.Center
Layout.preferredHeight: bottomRow.height
//visible: speedFlowChartProduct.status === Product.Registered
flat: true
font.pixelSize: height * 0.4
font.capitalization: Font.MixedCase
//% "Restore purchase"
text: qsTrId("#restorePurchase")
onClicked: inAppProductStore.restorePurchases()
}
Button {
id: contactSupportButton
Layout.alignment: Layout.Center
Layout.preferredHeight: bottomRow.height
flat: true
font.pixelSize: height * 0.4
font.capitalization: Font.MixedCase
//% "contact support"
text: qsTrId("#contact support")
onClicked: Qt.openUrlExternally("mailto:contact@itsblue.de")
}
}
}
}

View file

@ -1,124 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.15
Rectangle {
id: control
property var flowchartData: ({})
// always unlock in debug mode
//property bool unlocked: QT_DEBUG || appSettings.read("speedBackendPurchase") === "1"
property bool unlocked: Qt.platform.os !== "iso" || appSettings.read("speedBackendPurchase") === "1"
Component.onCompleted: {
console.warn("unlocked:", appSettings.read("speedBackendPurchase"))
}
state: "hidden"
anchors {
top: parent.top
bottom: parent.bottom
left: parent.right
}
width: parent.width
height: parent.height
color: Material.background
onStateChanged: reloadState()
onUnlockedChanged: reloadState()
function toggle() {
if(control.state === "hidden"){
control.state = "visible"
}
else {
control.state = "hidden"
}
}
function isVisible() {
return control.state === "visible"
}
function reloadState() {
if(state === "visible" && unlocked) {
speedFlowChartLoader.sourceComponent = speedFlowChartComponent
}
else if(state === "visible" && !unlocked) {
speedFlowChartLoader.sourceComponent = speedFlowChartLockerComponent
}
else {
speedFlowChartLoader.sourceComponent = undefined
}
}
Connections {
target: speedFlowChartProduct
function onPurchaseRestored() {
control.unlocked = appSettings.read("speedBackendPurchase") === "1"
}
function onPurchaseSucceeded() {
control.unlocked = appSettings.read("speedBackendPurchase") === "1"
}
}
Loader {
id: speedFlowChartLoader
anchors.fill: parent
Component {
id: speedFlowChartComponent
SpeedFlowChart {
anchors.fill: parent
anchors.margins: 10
flowchartData: control.flowchartData
}
}
Component {
id: speedFlowChartLockerComponent
SpeedFlowChartLocker {
}
}
}
states: [
State {
name: "hidden"
PropertyChanges {
target: control
opacity: 0
anchors.leftMargin: 0
}
},
State {
name: "visible"
PropertyChanges {
target: control
opacity: 1
anchors.leftMargin: -parent.width
}
}
]
transitions: [
Transition {
NumberAnimation {
properties: "opacity,scale,anchors.leftMargin"
duration: 200
easing.type: Easing.InOutQuad
}
}
]
}

View file

@ -1,79 +0,0 @@
import QtQuick 2.10
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.15
Row {
id: control
property bool active: (parseInt(widgetData[ "route_order" ]) === 0) && (widgetData["discipline"] === "speed") && (thisData["result_l"] !== undefined || thisData["result_r"] !== undefined)
property var thisData: widgetData["participants"][partDel.thisIndex]
height: parent.height
width: active ? parent.width - resultLa.width:0
enabled: active
Repeater {
id: generalResRep
model: 2
delegate: Item {
id: boulderGenResItm
anchors.verticalCenter: parent.verticalCenter
width: parent.width / ( generalResRep.model )
height: parent.height
visible: control.active
Rectangle {
anchors {
left: parent.left
}
width: 1
height: parent.height
visible: index === 0
color: Material.primaryTextColor
}
Rectangle {
anchors {
right: parent.right
}
width: 1
height: parent.height
color: Material.primaryTextColor
}
Label {
id: boulderGenResLa
property string thisKey: "result_"+(["l", "r"][index])
anchors.centerIn: parent
height: parent.height
width: parent.width * 0.9
fontSizeMode: Text.Fit
font.pixelSize: Math.abs( height * 0.6 )
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.bold: thisData["result_l"] !== undefined && thisData["result_r"] !== undefined && thisData[thisKey] === thisData["result"]
text: (control.thisData[thisKey] !== undefined && parseInt(control.thisData[thisKey]) > 0) ? control.thisData[thisKey] : ""
}
}
}
}

View file

@ -14,7 +14,6 @@ Item {
anchors.fill: parent
anchors.margins: 1
anchors.bottomMargin: indicator.height
spacing: width * 0.2
Repeater {
model: control.images.length

View file

@ -0,0 +1,141 @@
/*
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
//import QtQuick.Templates 2.04 as T
//import QtQuick.Controls.impl 2.04
import QtQuick.Controls.Material 2.3
import de.itsblue.blueRock 2.0
import "../Components"
Page {
id: root
property BRWidget data
property Component headerComponent
property alias selector: selectorPopup
property string subTitle
property bool titleIsPageTitle: true
property int status: -1
property bool ready: false
signal loadingFinished()
signal selectionFinished(int index, var data)
Component.onCompleted: {
console.log("TRYING TO LOAD")
var status = data.load()
console.log(data)
if(status !== BRWidget.Success)
ready = false
else
ready = true
loadingFinished()
}
function loadData(params) {
// params is an object and can contain: {
// comp: competitionId,
// person: personId,
// cat: categoryId,
// nation: nationString ('', 'GER', 'SUI')
// route: (int) round
// type: ('','starters', 'nat_team_ranking', 'sektionenwertung', 'regionalzentren'),
//}
var ret = serverConn.getWidgetData(params)
root.status = ret["status"]
if(ret["status"] === 200){
root.widgetData = ret["data"]
root.widgetType = checkWidgetType(params, root.widgetData)
if(widgetLd.load()){
root.ready = true
}
else {
//root.status = 901
root.ready = false
}
}
else if(ret["status"] === 404 && [WidgetPage.WidgetType.Registration, WidgetPage.WidgetType.Startlist, WidgetPage.WidgetType.Result].includes(root.widgetType) && root.params["route"] !== "") {
// if we get a 404 and have startlist, results or registration, the route was not found -> remove route and try again
root.params["route"] = ""
loadData(root.params)
return
}
else {
root.ready = false
}
app.errorCode = root.status
}
SelectorPopup {
id: selectorPopup
contentItem: ListView {
id: selectorLv
property int delegateHeight: 50
spacing: 10
clip: true
implicitWidth: selectorPopup.width
implicitHeight: model === undefined ? 0:(delegateHeight + spacing) * model.length
model: selectorPopup.dataObj !== undefined ? selectorPopup.dataObj:undefined
ScrollIndicator.vertical: ScrollIndicator {
parent: selectorLv.parent
anchors {
top: selectorLv.top
left: selectorLv.right
margins: 10
leftMargin: 3
bottom: selectorLv.bottom
}
}
delegate: Button {
id: catBt
width: selectorLv.width
height: text !== "" ? selectorLv.delegateHeight:0
flat: true
text: selectorPopup.dataObj[index].text
onClicked: {
selectorPopup.close()
control.selectionFinished(index, selectorPopup.dataObj[index].data)
}
}
}
}
}

View file

@ -0,0 +1,370 @@
/*
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 QtQuick.Layouts 1.3
import de.itsblue.blueRock 2.0
import "../Components"
BRWidgetPage {
id: control
title: control.data.title
headerComponent: RowLayout {
height: parent.height
//width: 10//childrenRect.width
spacing: 0
ToolButton {
id:yearToolBt
onClicked: control.changeSeason()
icon.name: "year"
}
ToolButton {
id: filterToolBt
onClicked: control.changeLeagues()
icon.name: "filter"
}
ToolButton {
id: cupToolBt
onClicked: control.openCup()
icon.name: "cup"
}
}
onLoadingFinished: {
calendarLv.autoScroll()
}
onSelectionFinished: {
if(data.task === "competition"){
loadingDl.open()
data.competition.currentCategory = data.category
app.openResults(data.competition)
loadingDl.close()
//app.openWidget({comp: data.comp, cat: data.cat, type:data.status === 4 ? 'starters':''})
}
else if(data.task === "season"){
loadingDl.open()
control.data.currentSeason = data.season
loadingDl.close()
}
else if(data.task === "cup1"){
control.openCup(data)
}
else if(data.task === "cup2"){
app.openWidget({cup: data.cup, cat: data.cat})
}
}
function changeSeason(){
var selectOptions = []
for(var i = 0; i < control.data.seasons.length; i++){
selectOptions.push({text: control.data.seasons[i].name, data:{task: "season", season: control.data.seasons[i]}})
}
selector.appear(selectOptions, qsTr("select season"))
}
function openCompetition(competition){
var selectOptions = []
var categories = competition.categories
for(var i = 0; i < categories.length; i++){
selectOptions.push({text: categories[i].name, data:{task: "competition", category: categories[i], competition: competition, /*TODO*/ status:categories.status}})
}
var infoUrls = competition.infosheetUrls
var infosheet = "";
if(infoUrls.length >= 1)
infosheet += ("<a href='" + infoUrls[0] + "'>" + qsTr('infosheet') + "</a>")
if(infoUrls.length === 2)
infosheet += (", <a href='" + infoUrls[1] + "'>" + qsTr('further infos') + "</a>")
var eventWebsite = competition.eventWebsiteUrl.toString() !== "" ? ("<a href='" + competition.eventWebsiteUrl + "'>" + qsTr('Event Website') + "</a>"):""
selector.appear(selectOptions, competition.name, eventWebsite + ((eventWebsite !== "" && infosheet !== "") ? ", ":"") + infosheet )
}
function openCup(data) {
var cups = control.data.currentSeason.cups
var prop
var selectOptions = []
var selectTitle = ""
if(data === undefined){
// opened for the first time -> select cup
selectTitle = qsTr("select cup")
for(var i = 0; i < cups.length; i++){
selectOptions.push({text: cups[i].name, data:{task: "cup1", cup: cups[i]}})
}
}
else if(data.task === "cup1"){
// opened for the second time -> select cat
var cup = data.cup
selectTitle = cup['name'] + ": " + qsTr("select category")
for(var c = 0; c < cup.categories.length; c++){
selectOptions.push({text: cup.categories[c].name, data:{task: "cup2", cup: data.cup, cat: cup.categories[c]}})
}
}
selector.appear(selectOptions, selectTitle)
}
function changeLeagues() {
var leagues = control.data.currentSeason.leagues
var selectOptions = []
for(var i = 0; i < leagues.length; i++) {
selectOptions.push( {text: leagues[i].name, data:{task:"leagues", league: leagues[i]}} )
}
//compCats.push( {"text": qsTr("Pinned"), "data": {"sort_rank":0, "cat_id":[-1]}} )
console.log("leagues: " + leagues)
filterSelectPu.appear(selectOptions, qsTr("Select Filters"), "")
}
DataListView {
id: calendarLv
anchors.fill: parent
model: control.data.currentSeason.competitions
onRefresh: control.data.load()
function autoScroll() {
// function to scroll to the next competition that is not already over
var compList = calendarLv.model
if(control.data.currentSeason.year === new Date().getFullYear()){
console.log("SCROLLING")
for(var i = 0; i < compList.length; i ++){
if(compList[i].endDate.getTime() < new Date().getTime()){
// end date is already over -> move the list view down!
calendarLv.positionViewAtIndex(i, ListView.Top)
}
}
}
}
delegate: ItemDelegate {
id: competitionDel
property var thisData: modelData
width: calendarLv.width
height: compDelCol.height + 10
enabled: thisData.categories.length > 0 || thisData.eventWebsiteUrl.toString() !== "" || thisData.infosheetUrls.length > 0
opacity: 0
scale: 0.9
onThisDataChanged: fadeInPa.start()
Behavior on height {
NumberAnimation {
duration: 400
}
}
onClicked: control.openCompetition(competitionDel.thisData)
ParallelAnimation {
id: fadeInPa
NumberAnimation { target: competitionDel; property: "opacity"; from: 0; to: 1.0; duration: 400 }
NumberAnimation { target: competitionDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 }
}
ParallelAnimation {
id: fadeOutPa
NumberAnimation { target: competitionDel; property: "opacity"; from: 1; to: 0; duration: 400 }
NumberAnimation { target: competitionDel; property: "scale"; from: 1; to: 0.8; duration: 400 }
}
Rectangle {
id: delBackroundRect
anchors.fill: parent
opacity: 0.5
color: competitionDel.thisData.league.color
}
Column {
id: compDelCol
anchors.centerIn: parent
width: parent.width * 0.97
spacing: 10
RowLayout {
width: parent.width
Label {
id: nameLa
width: parent.width
font.bold: true
wrapMode: Text.WordWrap
text: competitionDel.thisData.name
Layout.fillWidth: true
}
ToolButton {
id: bookmarkTb
icon.name: competitionDel.thisData.pinned ? "pinFilled":"pin"
onClicked: {
competitionDel.thisData.pinned = !competitionDel.thisData.pinned
}
Layout.alignment: Layout.Right
Behavior on icon.name {
SequentialAnimation {
NumberAnimation {
property: "scale"
target: bookmarkTb
duration: 75
to: 0.8
}
NumberAnimation {
property: "scale"
target: bookmarkTb
duration: 75
to: 1
}
}
}
}
}
Label {
id: dateLa
color: "grey"
text: competitionDel.thisData.dateSpan
}
}
Rectangle {
id: bottomLineRa
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
height: 1
color: "lightgrey"
}
}
section.property: "month"
section.delegate: ItemDelegate {
id: name
background: Rectangle {
color: "red"
}
width: parent.width
text: section
}
}
SelectorPopup {
id: filterSelectPu
contentItem: ListView {
id: selectorLv
property int delegateHeight: 50
spacing: 10
clip: true
implicitWidth: parent.width
implicitHeight: model === undefined ? 0:(delegateHeight + spacing) * model.length
model: filterSelectPu.dataObj
ScrollIndicator.vertical: ScrollIndicator {
parent: selectorLv.parent
anchors {
top: selectorLv.top
left: selectorLv.right
margins: 10
leftMargin: 3
bottom: selectorLv.bottom
}
}
delegate: CheckDelegate {
id: catBt
property var thisData: modelData
width: parent.width
height: text !== "" ? selectorLv.delegateHeight:0
checked: thisData.data.league.enabled
text: thisData.text
onClicked: {
if(thisData.data.league.state !== BRWidget.Loaded) {
loadingDl.open()
thisData.data.league.load()
loadingDl.close()
}
thisData.data.league.enabled = checked
}
}
}
}
}

View file

@ -1,298 +0,0 @@
import QtQuick 2.0
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QZXing 3.3
import QtMultimedia 5.12
import QtQuick.Shapes 1.12
import QtQuick.Controls.Material 2.12
import "../Components"
Page {
id: control
property string _statusText: ""
property string _statusColor: Material.primaryTextColor
property bool _freezeScanning: false
signal headerComponentChanged()
//% "Scan QR-Code"
title: qsTrId("#scanQrCode")
onFocusChanged: focus ? open() : close()
function open() {
_setDefaultStatusText()
control._freezeScanning = false
if(serverConn.isCameraPermissionGranted())
cameraLoader.sourceComponent = cameraComponent
else
cameraLoader.sourceComponent = noPermissionComponent
}
function close() {
cameraLoader.sourceComponent = null
}
function _setDefaultStatusText() {
//% "Place the Code in the center"
_statusText = qsTrId("#placeQrCodeInCenter")
_statusColor = Material.primaryTextColor
}
function _handleTag(tag) {
if(control._freezeScanning)
return
control._freezeScanning = true
//% "Plase wait"
control._statusText = qsTrId("#pleaseWait") + "..."
if(app.openWidgetFromUrl(tag))
control.close()
else {
//% "Invalid QR-Code"
control._statusText = qsTrId("#invalidQrCode")
control._statusColor = Material.color(Material.Red)
statusTextResetTimer.start()
control._freezeScanning = false
}
}
function _requestCameraPermission() {
var permissionGranted = serverConn.requestCameraPermission()
if(permissionGranted)
cameraLoader.sourceComponent = cameraComponent
}
contentItem: Loader {
id: cameraLoader
anchors.fill: parent
//asynchronous: true
sourceComponent: null
}
Component {
id: cameraComponent
Item {
anchors.fill: parent
Camera {
id: camera
captureMode: Camera.CaptureStillImage
imageProcessing.whiteBalanceMode: CameraImageProcessing.WhiteBalanceAuto
focus {
focusMode: Camera.FocusContinuous
focusPointMode: Camera.FocusPointCenter
}
}
FancyBusyIndicator {
anchors.centerIn: parent
}
VideoOutput {
id: videoOutput
x: 0
y: 0
width: parent.width
height: parent.height
fillMode: VideoOutput.PreserveAspectCrop
source: camera
filters: [ zxingFilter ]
focus : visible // to receive focus and capture key events when visible
autoOrientation: true
MouseArea {
anchors.fill: parent
onClicked: {
if (camera.lockStatus !== Camera.Unlocked)
camera.unlock();
camera.searchAndLock();
}
}
Rectangle {
anchors {
top: parent.top
left: parent.left
right: app.landscape() ? focusIndicatorRect.left : parent.right
bottom: app.landscape() ? parent.bottom : focusIndicatorRect.top
}
opacity: focusIndicatorRect.opacity
color: focusIndicatorRect.border.color
}
Rectangle {
id: focusIndicatorRect
anchors.centerIn: parent
width: Math.min(parent.height, parent.width)
height: width
border.width: width * 0.1
border.color: "#000000"
opacity: 0.5
color: "transparent"
}
Rectangle {
anchors {
bottom: focusIndicatorRect.bottom
bottomMargin: height * 0.5
horizontalCenter: focusIndicatorRect.horizontalCenter
}
width: (focusIndicatorRect.width - focusIndicatorRect.border.width * 2) * 0.8
height: focusIndicatorRect.border.width
radius: height * 0.3
color: Material.backgroundColor
Material.elevation: 10
Label {
anchors {
fill: parent
margins: height * 0.1
}
color: control._statusColor
font.pixelSize: height * 0.5
fontSizeMode: Text.Fit
minimumPixelSize: height * 0.2
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: control._statusText
}
}
Rectangle {
anchors {
top: app.landscape() ? parent.top : focusIndicatorRect.bottom
left: app.landscape() ? focusIndicatorRect.right : parent.left
right: parent.right
bottom: parent.bottom
}
opacity: focusIndicatorRect.opacity
color: focusIndicatorRect.border.color
}
}
}
}
Component {
id: noPermissionComponent
ColumnLayout {
//anchors.fill: parent
//anchors.margins: app.landscape() ? app.height * 0.1 : app.width * 0.1
property int columnWidth: control.width * 0.9
spacing: height * 0.02
Item {
Layout.fillHeight: true
}
Label {
id: noPermissionIcon
Layout.preferredWidth: parent.columnWidth
Layout.alignment: Layout.Center
font.pixelSize: app.landscape() ? parent.height * 0.25:parent.height * 0.15
font.family: fa5solid.name
horizontalAlignment: Text.AlignHCenter
text: "\uf3ed"
}
Label {
id: noPermissionText
Layout.preferredWidth: parent.columnWidth
Layout.alignment: Layout.Center
font.bold: true
font.pixelSize: noPermissionIcon.height * 0.2
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
//% "Camera access required"
text: qsTrId("#cameraPermissionDenied")
}
Label {
id: noPermissionDetailText
Layout.preferredWidth: parent.columnWidth
Layout.alignment: Layout.Center
font.pixelSize: noPermissionText.font.pixelSize * 0.7
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
//% "blueROCK needs to access your camera in order to scan QR-Codes. It will never record or store any photos or videos."
text: qsTrId("#cameraPermissionDeniedDetails")
}
Button {
id: grantPermissionButton
Layout.alignment: Layout.Center
//% "Allow access"
text: qsTrId("#allowAccess")
onClicked: control._requestCameraPermission()
}
Item {
Layout.fillHeight: true
}
}
}
QZXingFilter {
id: zxingFilter
decoder {
onTagFound: control._handleTag(tag)
enabledDecoders: QZXing.DecoderFormat_QR_CODE
}
}
Timer {
id: statusTextResetTimer
running: false
repeat: false
interval: 3000
onTriggered: _setDefaultStatusText()
}
}

View file

@ -0,0 +1,872 @@
/*
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.5
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Material 2.3
//import QtLocation 5.13
import QtPurchasing 1.12
import "../Components"
BRWidgetPage {
id: control
ready: true
title: data.name
titleIsPageTitle: true
subTitle: data.currentCategory.currentRound.name + " " + data.currentCategory.name + " " + qsTr("(Results)")
headerComponent: RowLayout {
id: headerComponent
height: parent.height
spacing: 0
ToolButton {
id: flowToolBt
visible: false // TOODO: speedFlowChart.enabled
enabled: true // TODO control.data.currentCategory.currentRound.getId() === -1 && Object.keys(control.widgetData['route_names']).length > 2
onClicked: {
if(speedFlowChartBackgroundRect.state === "hidden"){
speedFlowChartBackgroundRect.state ="visible"
control.positionViewAtBeginning()
}
else {
speedFlowChartBackgroundRect.state = "hidden"
}
}
icon.name: "flowchart"
}
ToolButton {
id: moreToolBt
onClicked: {
control.changeCategory()
}
icon.name: "menu"
}
}
onLoadingFinished: {
return;
if(resultLv.model.length > 0){
control.ready = true
control.status = 200
}
else {
control.ready = false
control.status = 901
}
if(!control.ready)
return
}
onSelectionFinished: {
if(data.task === "category"){
loadingDl.open()
control.data.currentCategory = data.category
control.data.load()
loadingDl.close()
}
else if(data.task === "season"){
loadingDl.open()
control.data.currentSeason = data.season
loadingDl.close()
}
else if(data.task === "cup1"){
control.openCup(data)
}
else if(data.task === "cup2"){
app.openWidget({cup: data.cup, cat: data.cat})
}
}
function changeCategory(){
var selectOptions = []
var categories = control.data.categories
for(var i = 0; i < categories.length; i++){
if(categories[i] === control.data.currentCategory)
continue
selectOptions.push({text: categories[i].name, data:{task: "category", category: categories[i]}})
}
selector.appear(selectOptions, qsTr("select cat"))
}
function changeRound(round) {
loadingDl.open()
control.data.currentCategory.currentRound = round
control.data.load()
loadingDl.close()
}
DataListView {
id: resultLv
anchors {
top: parent.top
topMargin: 0
left: parent.left
right: parent.right
bottom: routeSelectTb.top
}
model: control.data.results
onRefresh: {
control.data.load()
}
/* TODO onWidgetDataChanged: {
console.log("widget data changed")
if(control.widgetData['discipline'] === 'speed' && control.widgetData['route_order'] === "-1" && Object.keys(control.widgetData['route_names']).length > 2){
speedFlowChart.flowchartData = ({})
speedFlowChart.enabled = true
speedFlowChart.flowchartData = control.widgetData
}
else {
speedFlowChart.enabled = false
speedFlowChart.flowchartData = ({})
}
}*/
onContentYChanged: {
if(contentY > 0 && speedFlowChartBackgroundRect.state === "visible"){
resultLv.positionViewAtBeginning()
}
}
header: Item {}
delegate: ItemDelegate {
id: partDel
property int ind: index
property var thisData: modelData
enabled: speedFlowChartBackgroundRect.state === "hidden"
width: resultLv.width
height: 70
text: ""
opacity: 0
scale: 0.9
onThisDataChanged: {
fadeInPa.start()
}
onClicked: {
app.openWidget({person:thisData["PerId"]})
}
ParallelAnimation {
id: fadeInPa
NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 }
NumberAnimation { target: partDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 }
}
Rectangle {
id: partDelBackgroundRect
anchors.fill: parent
width: partDel.width
color: partDel.ind % 2 == 0 ? "white":"lightgrey"
opacity: 0.2
}
Column {
id: partDelCol
anchors.fill: parent
anchors.margins: 5
Row {
id: partDelFirstRow
width: parent.width
height: parent.height - partDelSecondRow.height
Label {
height: parent.height
width: text === "" ? parent.width * 0.08:parent.width * 0.1
fontSizeMode: Text.Fit
font.bold: true
font.pixelSize: height * 0.6//Math.abs( partDelSecondRow.height > 0 ? height * 0.6:height * 0.4 )
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: partDel.thisData.rank
}
Label {
height: parent.height
width: parent.width * 0.5
fontSizeMode: Text.Fit
font.bold: true
font.pixelSize: height * 0.6//Math.abs( partDelSecondRow.height > 0 ? height * 0.6:height * 0.4 )
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
text: partDel.thisData.athlete.firstName + " " + partDel.thisData.athlete.lastName + (partDel.thisData["start_number"] !== undefined ? (" (" + partDel.thisData["start_number"] + ")"):"")
}
Label {
height: parent.height
width: parent.width * 0.4
fontSizeMode: Text.Fit
font.bold: false
font.pixelSize: height * 0.6 //Math.abs( partDelSecondRow.height > 0 ? height * 0.4:height * 0.3 )
minimumPixelSize: height * 0.3 < 1 ? 1:height * 0.3
elide: "ElideRight"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: "<html>(<a href=\"" + (partDel.thisData.athlete.federationUrl === undefined ? "":partDel.thisData.athlete.federationUrl).toString() + "\">" + partDel.thisData.athlete.federation + "</a>)</html>"
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
}
Row {
id: partDelSecondRow
width: parent.width
height: resultLa.acitve ? parent.height / 2 : 0 // TODO multiResRow.active || multiGenResRow.active || resultLa.acitve ? parent.height / 2 : 0
/*
Row {
id: multiResRow
property bool active: false // TODO parseInt(widgetData[ "route_order" ]) > -1 && boulderResRep.model > 0
height: parent.height
width: active ? parent.width * 0.75:0
enabled: parseInt(widgetData[ "route_order" ]) > -1 && boulderResRep.model > 0
Repeater {
id: boulderResRep
model: parseInt(widgetData[ "route_num_problems" ])
function getDataForIcon(index){
var resultString = widgetData[ "participants" ][partDel.ind]["boulder"+(index+1)]
var resultList = []
if( resultString !== undefined){
resultString = resultString.replace("t", "")
resultString = resultString.replace("z", "")
resultString = resultString.replace("b", "")
resultList = resultString.split(" ")
while (resultList.length < 2){
resultList.unshift(0)
}
}
else {
resultList = [-1,-1]
}
return resultList
}
delegate: Item {
id: boulderResItm
anchors.verticalCenter: parent.verticalCenter
width: parent.width / ( boulderResRep.model )
height: parent.height
Canvas {
id: boulderResCv
property var resultData: boulderResRep.getDataForIcon(index)
onResultDataChanged: {
boulderResCv.requestPaint()
}
anchors.centerIn: parent
height: parent.height > parent.width ? parent.width * 0.9:parent.height * 0.9
width: height
onPaint: {
var width = 24//boulderResCv.width * 0.9
var height = width
var radius = width * 0.3
var offsetX = width * 0.05
var offsetY = height * 0.05
//console.log("drawing result rect with width: " + width + " and height: " + height)
var context = getContext("2d");
// clear all remainings from other routes
context.clearRect(0, 0, width, height);
context.beginPath();
context.moveTo(0 + offsetX + radius, 0 + offsetY);
// top line
context.lineTo(width - radius + offsetX, 0 + offsetY);
// top right corner
context.arc(width-radius + offsetX, radius + offsetY, radius, 1.5 * Math.PI, 0);
// right line
context.lineTo(width + offsetX, height - radius + offsetY);
// bottom right corner
context.arc(width-radius + offsetX, height - radius + offsetY, radius, 0, 0.5 * Math.PI);
// bottom line
context.lineTo(0 + radius + offsetX, height + offsetY);
// bottom left corner
context.arc(radius + offsetY, height - radius + offsetY, radius, 0.5 * Math.PI, Math.PI);
// left line
context.lineTo(0 + offsetX, radius + offsetY);
// top left corner
context.arc(radius + offsetX, radius + offsetY, radius, Math.PI, 1.5 * Math.PI);
// fill
if(resultData[0] !== -1) {
// if there is a result available -> draw background
context.fillStyle = "#b7b7b7";
}
else {
context.fillStyle = "transparent";
}
context.fill();
// outline
context.lineWidth = 1;
context.strokeStyle = '#424242';
context.stroke();
if(resultData[1] > 0){
// the first triangle
context.beginPath();
// top right corner
context.arc(width-radius + offsetX, radius + offsetY, radius, 1.75 * Math.PI, 0);
// right line
context.lineTo(width + offsetX, height - radius + offsetY);
// bottom right corner
context.arc(width-radius + offsetX, height - radius + offsetY, radius, 0, 0.5 * Math.PI);
// bottom line
context.lineTo(0 + radius + offsetX, height + offsetY);
// bottom left corner
context.arc(radius + offsetX, height - radius + offsetY, radius, 0.5 * Math.PI, 0.75 * Math.PI);
context.closePath();
context.fillStyle = "#44ed38";
context.fill();
// outline
context.lineWidth = 1;
context.strokeStyle = '#424242';
context.stroke();
if(resultData[0] > 0){
// the second triangle
context.beginPath();
// bottom left corner
context.arc(radius + offsetX, height - radius + offsetY, radius, 0.75 * Math.PI, 1 * Math.PI);
// left line
context.lineTo(0 + offsetX, radius + offsetY);
// top left corner
context.arc(radius + offsetX, radius + offsetY, radius, Math.PI, 1.5 * Math.PI);
// top line
context.lineTo(width - radius + offsetX, 0 + offsetY);
// top right corner
context.arc(width-radius + offsetX, radius + offsetY, radius, 1.5 * Math.PI, 1.75 * Math.PI);
context.closePath();
context.fillStyle = "#44ed38";
context.fill();
// outline
context.lineWidth = 1;
context.strokeStyle = '#424242';
context.stroke();
}
}
}
Label {
id: boulderResZoneLa
anchors {
right: parent.right
bottom: parent.bottom
margins: boulderResCv.height * 0.05
}
height: parent.height / 2
width: parent.width / 2
visible: parseInt(text) > 0
fontSizeMode: Text.Fit
font.pixelSize: height
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: boulderResCv.resultData[1]
}
Label {
id: boulderResTopLa
anchors {
left: parent.left
top: parent.top
margins: boulderResCv.height * 0.05
}
height: parent.height / 2
width: parent.width / 2
visible: parseInt(text) > 0
fontSizeMode: Text.Fit
font.pixelSize: height
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: boulderResCv.resultData[0]
}
}
}
}
}
Row {
id: multiGenResRow
property bool active: false // TODO ((parseInt(widgetData[ "route_order" ]) === -1) && (generalResRep.model > 0)) ? true:false
height: parent.height
width: active ? parent.width - resultLa.width:0
enabled: ((parseInt(widgetData[ "route_order" ]) === -1) && (generalResRep.model > 0)) ? true:false
Repeater {
id: generalResRep
property var routes: getRoutes()
model: routes.length
function getRoutes() {
var obj = widgetData["route_names"]
var routes = []
for(var prop in obj) {
// go through the whole array and search for data keys
if (obj.hasOwnProperty(prop) && prop > -1) {
routes.push([prop, obj[prop]])
//console.log("found " + obj[prop] + " at index " + prop)
}
}
routes.sort(function(a, b) {
return a[0] - b[0];
});
return routes
}
delegate: Item {
id: boulderGenResItm
anchors.verticalCenter: parent.verticalCenter
width: parent.width / ( generalResRep.model )
height: parent.height
visible: multiGenResRow.active
Rectangle {
anchors {
left: parent.left
}
width: 1
height: parent.height
visible: index === 0
color: "grey"
}
Rectangle {
anchors {
right: parent.right
}
width: 1
height: parent.height
color: "grey"
}
Label {
id: boulderGenResLa
anchors.centerIn: parent
height: parent.height
width: parent.width * 0.9
fontSizeMode: Text.Fit
font.pixelSize: Math.abs( height * 0.6 )
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: widgetData[ "participants" ][partDel.ind]["result"+(generalResRep.routes[index][0])] === undefined ? "":widgetData[ "participants" ][partDel.ind]["result"+(generalResRep.routes[index][0])]
}
}
}
}
*/
Label {
id: resultLa
property bool acitve: true // TODO ( boulderResRep.model > 0 || widgetData["discipline"] !== "boulder" ) && parseInt(widgetData[ "route_order" ]) > -1
width: enabled ? parent.width * 0.75:0
height: enabled ? parent.height:0
enabled: true //( boulderResRep.model > 0 || widgetData["discipline"] !== "boulder" ) && parseInt(widgetData[ "route_order" ]) > -1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
fontSizeMode: Text.Fit
font.pixelSize: Math.abs( height * 0.6 )
minimumPixelSize: 1
text: partDel.thisData.details.string()
}
}
}
}
PullRefresher {
target: resultLv
postRefreshDelay: 0
busyIndicator: FancyBusyIndicator {}
refreshPosition: height * 1.3
onRefreshRequested: {
resultLv.refresh()
}
}
}
RectangularGlow {
id: toolBarEffect
glowRadius: 3
spread: 0.2
color: "black"
opacity: 0.3
anchors.fill: routeSelectTb
}
TabBar {
id: routeSelectTb
property var tabs: control.data.currentCategory.rounds
property var tabIndexes: []
enabled: speedFlowChartBackgroundRect.state === "hidden"
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: tabs.length > 1 ? 50:0
width: parent.width
position: TabBar.Footer
currentIndex: tabs.indexOf(control.data.currentCategory.currentRound)
onTabsChanged: {
console.log("tabs: " + tabs + " rounds: " + control.data.currentCategory.rounds)
}
Repeater {
id: routeSelectButtonRep
model: routeSelectTb.tabs
onModelChanged: {
//routeSelectTb.setCurrentIndex(routeSelectTb.tabs.indexOf(control.data.currentCategory.currentRound))
}
delegate: TabButton {
text: modelData.name
width: Math.max(150, routeSelectTb.width / routeSelectButtonRep.model.length) //text.length * font.pixelSize
onClicked: {
control.changeRound(modelData)
return;
}
}
}
}
Rectangle {
id: speedFlowChartBackgroundRect
state: "hidden"
anchors {
top: control.top
bottom: control.bottom
left: control.right
}
width: control.width
height: control.height
color: Material.background
SpeedFlowChart {
id: speedFlowChart
anchors.fill: parent
enabled: false
flowchartData: ({})
onEnabledChanged: {
if(!enabled){
speedFlowChartBackgroundRect.state = 'hidden'
}
}
Rectangle {
id: speedFlowChartLockedOverlay
state: appSettings.read("speedBackendPurchase") === "1" ? "unlocked":"locked"
anchors.fill: parent
anchors.margins: -20
color: "white"
Connections {
target: speedFlowChartProduct
function onPurchaseRestored() {
speedFlowChartLockedOverlay.state = appSettings.read("speedBackendPurchase") === "1" ? "unlocked":"locked"
}
function onPurchaseSucceeded() {
speedFlowChartLockedOverlay.state = appSettings.read("speedBackendPurchase") === "1" ? "unlocked":"locked"
}
function onPurchaseFailed() {
purchaseBt.text = qsTr("Purchase failed")
purchaseBt.enabled = false
buttonTextResetTimer.start()
}
}
Timer {
id: buttonTextResetTimer
interval: 2000
running: false
repeat: false
onTriggered: {
purchaseBt.text = (speedFlowChartProduct.status === Product.Registered
? "Buy now for " + speedFlowChartProduct.price
: qsTr("this item is currently unavailable"))
purchaseBt.enabled = true
}
}
ColumnLayout {
id: lockedLayout
anchors {
fill: parent
topMargin: parent.height * 0.05
bottomMargin: parent.height * 0.1
rightMargin: parent.width * 0.1 + 20
leftMargin: parent.width * 0.1 + 20
}
//spacing: parent.height * 0.05
Image {
id: name
Layout.alignment: Layout.Center
Layout.preferredHeight: height
Layout.preferredWidth: width
width: lockedLayout.height * 0.1
height: width
mipmap: true
source: "qrc:/icons/lock.png"
}
Text {
Layout.fillWidth: true
height: parent.height * 0.05
text: qsTr("This is a premium feature.")
font.bold: true
font.pixelSize: parent.height * 0.05
fontSizeMode: Text.Fit
minimumPixelSize: 1
horizontalAlignment: Text.AlignHCenter
}
SwipeGallery {
Layout.fillHeight: true
Layout.fillWidth: true
images: ["qrc:/screenshots/SpeedFlowchartDemo/1.png","qrc:/screenshots/SpeedFlowchartDemo/2.png","qrc:/screenshots/SpeedFlowchartDemo/3.png"]
}
Button {
id: purchaseBt
Layout.alignment: Layout.Center
enabled: speedFlowChartProduct.status === Product.Registered
text: speedFlowChartProduct.status === Product.Registered
? "Buy now for " + speedFlowChartProduct.price
: qsTr("this item is currently unavailable")
icon.name: "buy"
//display: AbstractButton.TextBesideIcon
onClicked: speedFlowChartProduct.purchase()
}
}
states: [
State {
name: "unlocked"
PropertyChanges {
target: speedFlowChartLockedOverlay
visible: false
}
},
State {
name: "locked"
PropertyChanges {
target: speedFlowChartLockedOverlay
visible: true
}
}
]
}
}
states: [
State {
name: "hidden"
PropertyChanges {
target: speedFlowChartBackgroundRect
opacity: 0
anchors.leftMargin: 0
}
},
State {
name: "visible"
PropertyChanges {
target: speedFlowChartBackgroundRect
opacity: 1
anchors.leftMargin: -parent.width
}
}
]
transitions: [
Transition {
NumberAnimation {
properties: "opacity,scale,anchors.leftMargin"
duration: 200
easing.type: Easing.InOutQuad
}
}
]
}
}

View file

@ -16,10 +16,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.9
import QtQuick 2.0
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.0
import QtQuick.Controls.Material 2.12
import de.itsblue.blueRock 2.0
import "../Components"
@ -27,152 +26,86 @@ Page {
id: root
title: "start"
property Component headerComponent: null
signal headerComponentChanged()
BlueRockBadge {
id: headerBadge
Label {
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: root.height * 0.03
}
height: app.landscape() ? menuGr.buttonSize * 0.2:menuGr.buttonSize * 0.3
font.pixelSize: anchors.topMargin
font.bold: true
text: "blueROCK"
}
GridLayout {
Grid {
id: menuGr
anchors.centerIn: parent
rows: app.landscape() ? 1:2
columns: app.landscape() ? 2:1
rows: app.landscape() ? 1:3
columns: app.landscape() ? 3:1
rowSpacing: app.landscape() ? parent.width * 0.1:headerBadge.anchors.topMargin
columnSpacing: rowSpacing
spacing: !app.landscape() ? parent.height * 0.08:parent.width * 0.1
property int buttonSize: app.landscape() ? parent.width * 0.2:parent.height * 0.19
property int buttonSize: app.landscape() ? parent.width * 0.2:parent.height * 0.2
FancyButton {
id: ifscBt
height: menuGr.buttonSize
width: height
image: "qrc:/icons/ifsc.png"
onClicked: app.openCalendar(BRWidget.IFSC)
}
FancyButton {
id: davBt
Layout.preferredHeight: menuGr.buttonSize
Layout.preferredWidth: menuGr.buttonSize
Layout.alignment: Layout.Center
height: menuGr.buttonSize
width: height
image: Material.theme === Material.Dark ? "qrc:/icons/dav-dark.png":"qrc:/icons/dav.png"
onClicked: {
app.openWidget({nation:"GER"})
}
image: "qrc:/icons/dav.png"
onClicked: app.openCalendar(BRWidget.DAV)
}
FancyButton {
id: sacBt
Layout.preferredHeight: menuGr.buttonSize
Layout.preferredWidth: menuGr.buttonSize
Layout.alignment: Layout.Center
height: menuGr.buttonSize
width: height
image: Material.theme === Material.Dark ? "qrc:/icons/sac-dark.png":"qrc:/icons/sac.png"
image: "qrc:/icons/sac.png"
onClicked: {
app.openWidget({nation:"SUI"})
}
onClicked: app.openCalendar(BRWidget.SAC)
}
}
Grid {
id: footerMenu
Label {
anchors {
bottom: parent.bottom
margins: headerBadge.anchors.topMargin
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: root.height * 0.03
}
width: app.landscape() ? childrenRect.width : parent.width * 0.8
height: app.landscape() ? headerBadge.height : Math.min(headerBadge.height * 2, width * 0.27)
width: parent.width * 0.9
height: anchors.bottomMargin
columnSpacing: height * 0.1
wrapMode: "Wrap"
horizontalAlignment: Text.AlignHCenter
columns: app.landscape() ? 4:2
rows: app.landscape() ? 1:2
text: "Resultservice and rankings provided by <a href='http://www.digitalROCK.de'>digital ROCK</a>."
Repeater {
id: buttonRepeater
property var buttons: [
//% "IFSC results"
["\uf059", qsTrId("#ifscResults"), ifscDisclaimerDialog.open],
[
"\uf042",
Material.theme === Material.Light ?
//% "Dark mode"
qsTrId("#darkMode"):
//% "Light mode"
qsTrId("#lightMode"),
app.toggleDarkMode
],
//% "About blueROCK"
["\uf05a", qsTrId("#aboutBluerock"), aboutBluerockDisclaimerDialog.open],
["\uf029", qsTrId("#scanQrCode"), function(){
mainStack.push("qrc:/Pages/QrCodeScanPage.qml")
}],
]
model: buttons
delegate: Item {
width: app.landscape() ? footerMenuButton.implicitWidth : footerMenu.width * 0.5 - (footerMenu.columnSpacing / 2)
height: app.landscape() ? footerMenu.height : footerMenu.height * 0.5 - (footerMenu.rowSpacing / 2)
Button {
id: footerMenuButton
property bool isLeft: index % 2 === 0
anchors {
right: isLeft && !app.landscape() ? parent.right : undefined
left: isLeft && !app.landscape() ? undefined : parent.left
centerIn: app.landscape() ? parent : undefined
}
height: parent.height
flat: true
font.family: fa5solid.name
font.pixelSize: height * 0.4
font.capitalization: Font.MixedCase
//horizontalAlignment: isLeft ? Text.AlignRight : Text.AlignLeft
text: isLeft && !app.landscape() ? modelData[1] + " " + modelData[0] : modelData[0] + " " + modelData[1]
onClicked: buttonRepeater.buttons[index][2]()
}
}
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
DisclaimerDialog {
id: ifscDisclaimerDialog
Material.theme: root.Material.theme
//% "Where are the IFSC results?"
title: qsTrId("#ifscDisclaimerTitle")
//% "Unfortunately, the IFSC has restricted the access to their data and <b>is not willing to share results with blueROCK anymore</b>. Because of this, blueROCK is no longer able to access and display IFSC results.<br><br>You can find current IFSC results <a href=\"https://ifsc.results.info\">over here</a> and on <a href=\"https://ifsc-climbing.org\">their website</a>."
content: qsTrId("#ifscDisclaimer")
}
DisclaimerDialog {
id: aboutBluerockDisclaimerDialog
Material.theme: root.Material.theme
//% "privacy policy"
title: "blueROCK v" + APP_VERSION + "<br>By <a href=\"https://itsblue.de\">Itsblue Development</a>, <a href=\"https://itsblue.de/apps/bluerock/privacypolicy.html\">" + qsTrId("#privacyPolicy") + "</a>"
//% "This app was built using the <a href='https://qt.io'>Qt Framework</a> licensed under the <a href='https://www.gnu.org/licenses/lgpl-3.0.en.html'>GNU lgplV3 license</a>.<br><br>This app is open source and licensed under the <a href='https://www.gnu.org/licenses/agpl-3.0.en.html'>GNU agplV3 license</a>, the source code can be found <a href='https://itsblue.dev/blueROCK/app'>here</a>.<br><br>Resultservice and rankings provided by <a href='http://www.digitalROCK.de'>digital ROCK</a>."
content: qsTrId("#aboutBluerockDisclaimer")
}
}

View file

@ -38,9 +38,7 @@ Page {
Result,
Ranking,
Aggregated, // not yet implemented
Invalid
Aggregated // not yet implemented
}
title: widgetLd.item !== null && widgetLd.item.hasOwnProperty('title') ? widgetLd.item['title']:""
@ -48,10 +46,6 @@ Page {
property bool titleIsPageTitle: widgetLd.item !== null && widgetLd.item.hasOwnProperty('titleIsPageTitle') ? widgetLd.item['titleIsPageTitle']:false
property Component headerComponent: widgetLd.item !== null && widgetLd.item.hasOwnProperty('headerComponent') ? widgetLd.item['headerComponent']:null
function onBackRequested() {
return !widgetLd.item.hasOwnProperty('onBackRequested') || widgetLd.item.onBackRequested()
}
property var params
property int status: -1
@ -74,6 +68,7 @@ Page {
// route: (int) round
// type: ('','starters', 'nat_team_ranking', 'sektionenwertung', 'regionalzentren'),
//}
var ret = serverConn.getWidgetData(params)
root.status = ret["status"]
@ -81,11 +76,7 @@ Page {
if(ret["status"] === 200){
root.widgetData = ret["data"]
root.widgetType = checkWidgetType(params, root.widgetData)
if(widgetType === WidgetPage.WidgetType.Invalid) {
root.ready = false
root.status = 906
}
else if(widgetLd.load()){
if(widgetLd.load()){
root.ready = true
}
else {
@ -93,13 +84,7 @@ Page {
root.ready = false
}
}
else if(ret["status"] === 404 &&
[
WidgetPage.WidgetType.Registration,
WidgetPage.WidgetType.Startlist,
WidgetPage.WidgetType.Result
].includes(root.widgetType) &&
root.params["route"] !== "") {
else if(ret["status"] === 404 && [WidgetPage.WidgetType.Registration, WidgetPage.WidgetType.Startlist, WidgetPage.WidgetType.Result].includes(root.widgetType) && root.params["route"] !== "") {
// if we get a 404 and have startlist, results or registration, the route was not found -> remove round and try again
root.params["route"] = ""
loadData(root.params)
@ -129,10 +114,6 @@ Page {
}
function areParamsValid() {
}
function checkWidgetType(params, widgetData){
var widgetType
@ -179,25 +160,10 @@ Page {
// aggregated
widgetType = WidgetPage.WidgetType.Aggregated
}
else {
widgetType = WidgetPage.WidgetType.Invalid
}
return widgetType
}
function encodeQueryData(data) {
const ret = [];
for (let d in data)
ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
return ret.join('&');
}
function shareWidget(compName) {
var url = "https://l.bluerock.dev/?" + encodeQueryData(params)
sharePu.appear(url, compName)
}
Loader {
id: widgetLd
@ -231,6 +197,9 @@ Page {
delete(widgetLd.sourceComponent)
return false
}
//
}
function getFile(widgetType) {
@ -272,19 +241,107 @@ Page {
}
SelectorPopup {
Dialog {
id: selectorPu
Material.theme: root.Material.theme
property var dataObj
property string subTitle: ""
contentItem: ListView {
signal selectionFinished(int index, var data)
x: 0 //root.width / 2 - width / 2
y: root.height - selectorPu.height * 0.7//root.height - height //root.height / 2 - height / 2
opacity: 0
width: root.width
height: selectorLv.implicitHeight
modal: true
focus: true
title: ""
header: Column {
id: selectorPuHeaderCol
width: parent.width
Label {
id: headerLa
visible: selectorPu.title
width: parent.width
elide: "ElideRight"
padding: 24
bottomPadding: 0
font.bold: true
font.pixelSize: 16
background: Rectangle {
radius: 2
color: selectorPu.Material.dialogColor
clip: true
}
text: selectorPu.title
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
Label {
id: headerSubLa
visible: selectorPu.subTitle
width: parent.width
elide: "ElideRight"
padding: 24
topPadding: 5
bottomPadding: 0
font.bold: true
font.pixelSize: 16
background: Rectangle {
radius: 2
color: selectorPu.Material.dialogColor
clip: true
}
text: selectorPu.subTitle
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
}
function appear(dataObj, title, subTitle) {
if(dataObj.length > 0){
selectorPu.dataObj = dataObj
}
else {
selectorPu.dataObj = undefined
}
selectorPu.title = title
selectorPu.subTitle = subTitle === undefined ? "":subTitle
selectorPu.open()
}
ListView {
id: selectorLv
property int delegateHeight: 50
spacing: 10
clip: true
implicitHeight: model === 0 ? 0:(delegateHeight + spacing) * model
anchors.fill: parent
implicitWidth: parent.width
implicitHeight: root.height * 0.7 < ( (delegateHeight + spacing) * model ) ? root.height * 0.7 : (delegateHeight + spacing) * model + 100
model: selectorPu.dataObj !== undefined ? selectorPu.dataObj.length:0
@ -297,12 +354,13 @@ Page {
leftMargin: 3
bottom: selectorLv.bottom
}
}
delegate: Button {
id: catBt
width: selectorLv.width
width: parent.width
height: text !== "" ? selectorLv.delegateHeight:0
flat: true
@ -315,11 +373,32 @@ Page {
}
}
}
enter: Transition {
NumberAnimation {
property: "opacity";
//from: 0.0;
to: 1.0
}
NumberAnimation {
property: "y"
//from: root.height - selectorPu.height * 0.7
to: root.height - selectorPu.height
}
}
exit: Transition {
NumberAnimation {
property: "opacity";
//from: 1.0;
to: 0.0
}
NumberAnimation {
property: "y"
//from: root.height - selectorPu.height
to: root.height - selectorPu.height * 0.7
}
}
}
SharePopup {
id: sharePu
Material.theme: root.Material.theme
}
}

View file

@ -19,7 +19,6 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.12
import "../Components"
@ -28,12 +27,12 @@ DataListView {
property bool ready
//% "calendar"
property string title: (params.nation === "ICC" ? "IFSC":params.nation === "GER" ? "DAV":"SAC") + " " + qsTrId("#calendar") + " " + control.year
property string title: (params.nation === "ICC" ? "IFSC":params.nation === "GER" ? "DAV":"SAC") + " " + qsTr("calendar") + " " + control.year
property Component headerComponent: RowLayout {
height: parent.height
//width: 10//childrenRect.width
spacing: 0
ToolButton {
@ -43,9 +42,7 @@ DataListView {
control.changeYear()
}
text: "\uf133"
font.family: fa5solid.name
icon.name: "year"
}
ToolButton {
@ -64,19 +61,16 @@ DataListView {
}
}
//% "Favorites"
compCats.push( {"text": qsTrId("#favorites"), "data": {"sort_rank":0, "cat_id":[-1]}} )
compCats.push( {"text": qsTr("Pinned"), "data": {"sort_rank":0, "cat_id":[-1]}} )
compCats.sort(function(a, b) {
return a['data']['sort_rank'] - b['data']['sort_rank'];
});
//% "Select filters"
filterSelectPu.appear(compCats, qsTrId("#selectFilters"), "")
filterSelectPu.appear(compCats, qsTr("Select Filters"), "")
}
text: "\uf0b0"
font.family: fa5solid.name
icon.name: "filter"
}
ToolButton {
@ -86,8 +80,7 @@ DataListView {
control.openCup()
}
text: "\uf091"
font.family: fa5solid.name
icon.name: "cup"
}
}
@ -113,15 +106,10 @@ DataListView {
initFilters()
initFavorites()
if(model && widgetData["competitions"].length > 0){
if(model){
control.status = 200
control.ready = true
}
else if(widgetData["competitions"].length === 0) {
control.status = 404
control.ready = false
}
else {
control.ready = false
control.status = 901
@ -163,7 +151,7 @@ DataListView {
if(endDate.getTime() < new Date().getTime()){
// end date is already over -> move the list view down!
control.positionViewAtIndex(i - 2, ListView.Top)
control.positionViewAtIndex(i, ListView.Top)
//console.log("moving down!")
}
}
@ -205,18 +193,13 @@ DataListView {
var infoUrls = getCompInfoUrls(compIndex)
var infosheet = "";
if(infoUrls.length >= 1)
//% "Infosheet"
infosheet += ("<a href='" + getCompInfoUrls(compIndex)[0] + "'>" + qsTrId("#infosheet") + "</a>")
infosheet += ("<a href='" + getCompInfoUrls(compIndex)[0] + "'>" + qsTr('infosheet') + "</a>")
if(infoUrls.length === 2)
//% "Further infos"
infosheet += (", <a href='" + getCompInfoUrls(compIndex)[1] + "'>" + qsTrId("#furtherInfos") + "</a>")
infosheet += (", <a href='" + getCompInfoUrls(compIndex)[1] + "'>" + qsTr('further infos') + "</a>")
console.log("Infosheet: " + infosheet)
var eventWebsite = control.widgetData["competitions"][compIndex]["homepage"] !== undefined ?
//% "Event website"
("<a href='" + control.widgetData["competitions"][compIndex]["homepage"] + "'>" + qsTrId("#eventWebsite") + "</a>"):""
var eventWebsite = control.widgetData["competitions"][compIndex]["homepage"] !== undefined ? ("<a href='" + control.widgetData["competitions"][compIndex]["homepage"] + "'>" + qsTr('Event Website') + "</a>"):""
selector.appear(selectOptions, control.widgetData["competitions"][compIndex]['name'], eventWebsite + ((eventWebsite !== "" && infosheet !== "") ? ", ":"") + infosheet )
}
@ -232,8 +215,7 @@ DataListView {
}
}
//% "Select year"
selector.appear(selectOptions, qsTrId("#selectYear"))
selector.appear(selectOptions, qsTr("select year"))
}
function openCup(state, data) {
@ -245,8 +227,7 @@ DataListView {
if(state === undefined){
// opened for the first time -> select cup
//% "Select cup"
selectTitle = qsTrId("#selectCup")
selectTitle = qsTr("select cup")
cups.sort(function(a, b) {
return parseInt(b["SerId"]) - parseInt(a["SerId"]);
@ -274,8 +255,7 @@ DataListView {
return
}
//% "Select category"
selectTitle = cup['name'] + ": " + qsTrId("#selectCategory")
selectTitle = cup['name'] + ": " + qsTr("select category")
// build a list with all cat in the cup out of the cat keys (rkey) given in the cup.cats
for(prop in cup['cats']){
@ -389,8 +369,9 @@ DataListView {
Connections {
target: parent.selector
function onSelectionFinished(index, data) {
onSelectionFinished: {
if(data.comp !== undefined){
//console.log(data.status)
app.openWidget({comp: data.comp, cat: data.cat, type:data.status === 4 ? 'starters':''})
}
else if(data.year !== undefined){
@ -404,10 +385,6 @@ DataListView {
app.openWidget({cup: data.cup, cat: data.cat})
}
}
function onLinkActivated(link) {
Qt.openUrlExternally(link)
}
}
header: Item {
@ -422,22 +399,281 @@ DataListView {
height: 10
}
delegate: CompetitionCalendarDelegate {
delegate: ItemDelegate {
id: competitionDel
property bool over
property var thisData: control.widgetData["competitions"][index]
property string name: thisData["name"]
property string date: thisData["date_span"]
property var cats: thisData["cats"]
property int catId: thisData["cat_id"] === undefined ? 0:thisData["cat_id"]
property bool thisIsFavored: control.compFavorites.indexOf(parseInt(thisData['WetId'])) >= 0
property bool includedByFavorites: control.displayedCompCats.indexOf(-1) >= 0 && thisIsFavored
property bool includedByFilter: control.displayedCompCats.indexOf(parseInt(thisData['cat_id'])) >= 0
property bool thisIsVisible: includedByFavorites || includedByFilter
function updateVisibility() {
competitionDel.includedByFilter = control.displayedCompCats.indexOf(parseInt(competitionDel.thisData['cat_id'])) >= 0
competitionDel.thisIsFavored = control.compFavorites.indexOf(parseInt(thisData['WetId'])) >= 0
competitionDel.includedByFavorites = control.displayedCompCats.indexOf(-1) >= 0 && thisIsFavored
}
width: parent.width
height: thisIsVisible ? compDelCol.height + 10 : 0
enabled: ((thisData["cats"] !== undefined && thisData["cats"].length > 0) || competitionDel.thisData["homepage"] !== undefined || getCompInfoUrls(index).length > 0) && height > 0
//visible: includedByFilter
opacity: 0
scale: 0.9
Connections {
target: control
onDisplayedCompCatsChanged: {
competitionDel.updateVisibility()
}
onCompFavoritesChanged: {
competitionDel.updateVisibility()
}
}
onThisDataChanged: {
if(thisIsVisible){
fadeInPa.start()
}
}
onThisIsVisibleChanged: {
if(thisIsVisible){
fadeInPa.start()
}
else {
fadeOutPa.start()
}
}
Behavior on height {
NumberAnimation {
duration: 400
}
}
onClicked: {
control.openComp(index)
}
ParallelAnimation {
id: fadeInPa
NumberAnimation { target: competitionDel; property: "opacity"; from: 0; to: 1.0; duration: 400 }
NumberAnimation { target: competitionDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 }
}
ParallelAnimation {
id: fadeOutPa
NumberAnimation { target: competitionDel; property: "opacity"; from: 1; to: 0; duration: 400 }
NumberAnimation { target: competitionDel; property: "scale"; from: 1; to: 0.8; duration: 400 }
}
Rectangle {
id: delBackroundRect
anchors.fill: parent
opacity: 0.5
color: control.getCompCatData(catId) === undefined ? "white":control.getCompCatData(catId)["bgcolor"]
}
Column {
id: compDelCol
anchors.centerIn: parent
width: parent.width * 0.97
spacing: 10
RowLayout {
width: parent.width
Label {
id: nameLa
width: parent.width
font.bold: true
wrapMode: Text.WordWrap
text: name
Layout.fillWidth: true
}
ToolButton {
id: bookmarkTb
icon.name: competitionDel.thisIsFavored ? "pinFilled":"pin"
onClicked: {
control.editFavorites(!competitionDel.thisIsFavored, parseInt(thisData['WetId']))
}
Layout.alignment: Layout.Right
Behavior on icon.name {
SequentialAnimation {
NumberAnimation {
property: "scale"
target: bookmarkTb
duration: 75
to: 0.8
}
NumberAnimation {
property: "scale"
target: bookmarkTb
duration: 75
to: 1
}
}
}
}
}
Label {
id: dateLa
color: "grey"
text: date
}
}
Rectangle {
id: bottomLineRa
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
height: 1
color: "lightgrey"
}
}
SelectorPopup {
section.property: "month"
section.delegate: ItemDelegate {
id: name
background: Rectangle {
color: "red"
}
width: parent.width
text: section
}
Dialog {
id: filterSelectPu
Material.theme: control.Material.theme
property var dataObj
property string subTitle: ""
contentItem: ListView {
signal selectionFinished(int index, var data)
x: 0 - control.anchors.leftMargin //root.width / 2 - width / 2
y: root.height - filterSelectPu.height * 0.7//root.height - height //root.height / 2 - height / 2
opacity: 0
width: control.width + control.anchors.leftMargin + control.anchors.rightMargin
height: selectorLv.implicitHeight
modal: true
focus: true
title: ""
function appear(dataObj, title, subTitle) {
if(dataObj.length > 0){
filterSelectPu.dataObj = dataObj
filterSelectPu.title = title
filterSelectPu.subTitle = subTitle === undefined ? "":subTitle
filterSelectPu.open()
}
}
header: Column {
id: filterSelectPuHeaderCol
width: parent.width
Label {
id: headerLa
visible: filterSelectPu.title
width: parent.width
elide: "ElideRight"
padding: 24
bottomPadding: 0
font.bold: true
font.pixelSize: 16
background: Rectangle {
radius: 2
//color: filterSelectPu.Material.dialogColor
clip: true
}
text: filterSelectPu.title
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
Label {
id: headerSubLa
visible: filterSelectPu.subTitle
width: parent.width
elide: "ElideRight"
padding: 24
topPadding: 5
bottomPadding: 0
font.bold: true
font.pixelSize: 16
background: Rectangle {
radius: 2
//color: filterSelectPu.Material.dialogColor
clip: true
}
text: filterSelectPu.subTitle
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
}
ListView {
id: selectorLv
property int delegateHeight: 50
spacing: 10
clip: true
implicitHeight: model === 0 ? 0:(delegateHeight + spacing) * model
anchors.fill: parent
implicitWidth: parent.width
implicitHeight: root.height * 0.7 < ( (delegateHeight + spacing) * model ) ? root.height * 0.7 : (delegateHeight + spacing) * model + 100
model: filterSelectPu.dataObj !== undefined ? filterSelectPu.dataObj.length:0
@ -450,12 +686,13 @@ DataListView {
leftMargin: 3
bottom: selectorLv.bottom
}
}
delegate: CheckDelegate {
id: catBt
width: selectorLv.width
width: parent.width
height: text !== "" ? selectorLv.delegateHeight:0
//flat: true
@ -468,7 +705,7 @@ DataListView {
Connections {
target: control
function onDisplayedCompCatsChanged() {
onDisplayedCompCatsChanged: {
//console.log("filters changed")
//competitionDel.visible = control.displayedCompCats.indexOf(parseInt(competitionDel.thisData['cat_id'])) >= 0
checked = getCheckedState()
@ -496,5 +733,31 @@ DataListView {
}
}
}
enter: Transition {
NumberAnimation {
property: "opacity";
//from: 0.0;
to: 1.0
}
NumberAnimation {
property: "y"
//from: root.height - filterSelectPu.height * 0.7
to: root.height - filterSelectPu.height
}
}
exit: Transition {
NumberAnimation {
property: "opacity";
//from: 1.0;
to: 0.0
}
NumberAnimation {
property: "y"
//from: root.height - filterSelectPu.height
to: root.height - filterSelectPu.height * 0.7
}
}
}
}

View file

@ -19,7 +19,6 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.3
import QtGraphicalEffects 1.0
import "../Components"
@ -35,15 +34,6 @@ Page {
property var widgetData: currentWidgetData
property Component headerComponent: ToolButton {
id: shareToolBt
onClicked: shareWidget(control.title)
text: "\uf1e0"
font.family: fa5solid.name
}
Component.onCompleted: {
control.ready = true
control.status = 200
@ -53,11 +43,25 @@ Page {
id: mainSv
anchors.fill: parent
anchors.margins: 20
anchors.margins: 10
anchors.rightMargin: 14
contentWidth: parent.width - anchors.margins * 2
contentWidth: parent.width - anchors.leftMargin - anchors.rightMargin
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
ScrollBar.vertical: ScrollBar {
anchors {
top: mainSv.top
left: mainSv.right
margins: 10
leftMargin: 3
bottom: mainSv.bottom
}
width: 8
active: true
}
Column {
id: mainCol
@ -68,61 +72,25 @@ Page {
height: control.height * 0.3
width: parent.width
spacing: photoContainerItem.width * 0.1
Image {
id: photo
property bool ready: false
Item {
id: photoContainerItem
anchors.verticalCenter: parent.verticalCenter
height: parent.height * 0.9
width: photo.status === Image.Null || photo.status === Image.Error ? 0:parent.width * 0.4
width: status === Image.Null || status === Image.Error ? 0:parent.width * 0.5
RectangularGlow {
id: effect
visible: photo.ready
anchors.fill: photo
glowRadius: 1
opacity: 0.4
color: "black"
cornerRadius: photo.width * 0.2
}
fillMode: Image.PreserveAspectFit
Image {
id: photo
property bool ready: photo.status === Image.Ready
source: widgetData["photo"] === undefined ? "":widgetData["photo"].replace("https", "http").replace("www.digitalrock.de", "egw.ifsc-climbing.org")
asynchronous: true
FancyBusyIndicator {
height: width
anchors.centerIn: parent
height: parent.height * 0.9
width: height * 0.7
fillMode: Image.PreserveAspectCrop
mipmap: true
source: widgetData["photo"] === undefined ?
"":
widgetData["photo"].replace("https", "http")
asynchronous: true
FancyBusyIndicator {
height: width
anchors.centerIn: parent
opacity: photo.status === Image.Loading
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Item {
width: photo.width
height: photo.height
Rectangle {
anchors.centerIn: parent
width: photo.width
height: photo.height
radius: photo.width * 0.2
}
}
}
opacity: photo.status === Image.Loading
}
}
@ -131,7 +99,7 @@ Page {
anchors.verticalCenter: parent.verticalCenter
height: parent.height * 0.9
width: parent.width - photoContainerItem.width * 1.1
width: parent.width - photo.width
Label {
@ -199,8 +167,7 @@ Page {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
//% "Age"
text: qsTrId("#age") + ": " + widgetData["age"]
text: qsTr("age") + ": " + widgetData["age"]
}
Label {
@ -216,8 +183,7 @@ Page {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
//% "Year of birth"
text: qsTrId("#yearOfBirth") + ": " + widgetData["birthdate"]
text: qsTr("year of birth") + ": " + widgetData["birthdate"]
}
Label {
@ -233,8 +199,7 @@ Page {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
//% "City"
text: qsTrId("#city") + ": " + widgetData["city"]
text: qsTr("city") + ": " + widgetData["city"]
}
}
@ -249,9 +214,7 @@ Page {
wrapMode: Label.Wrap
text: widgetData["freetext"] === undefined ?
"":
(widgetData["freetext"] + "<br>")
text: widgetData["freetext"] === undefined ? "":widgetData["freetext"]
}
@ -261,9 +224,9 @@ Page {
anchors.horizontalCenter: parent.horizontalCenter
height: 1
width: parent.width
width: parent.width * 0.9
color: Material.foreground
color: "black"
}
@ -275,11 +238,7 @@ Page {
flat: true
text: bestResultsRep.showAllResults ?
//% "Show best results"
qsTrId("#showBestResults"):
//% "Show all results"
qsTrId("#showAllResults")
text: bestResultsRep.showAllResults ? qsTr("show best results"):qsTr("show all results")
onClicked: {
bestResultsRep.showAllResults = !bestResultsRep.showAllResults
@ -348,7 +307,6 @@ Page {
width: parent.width
height: 50
spacing: width * 0.05
opacity: 0
scale: 0.9
@ -366,11 +324,11 @@ Page {
Label {
id: bestResultRankLa
width: parent.width * 0.1
width: parent.width * 0.2
height: parent.height
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
horizontalAlignment: Text.AlignHCenter
fontSizeMode: Text.Fit
minimumPixelSize: 1
@ -387,7 +345,7 @@ Page {
height: parent.height
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
horizontalAlignment: Text.AlignHCenter
fontSizeMode: Text.Fit
minimumPixelSize: height * 0.3
@ -412,7 +370,7 @@ Page {
scale: 0.8
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignRight
horizontalAlignment: Text.AlignHCenter
fontSizeMode: Text.Fit
minimumPixelSize: 1

View file

@ -27,25 +27,15 @@ DataListView {
property bool ready
property string title: control.widgetData['comp_name']
//% "(Ranking)"
property string subTitle: qsTrId("#rankingHeadline") + " after " + control.widgetData['route_name']
property string subTitle: qsTr("(Ranking)") + " after " + control.widgetData['route_name']
property bool titleIsPageTitle: true
property var widgetData: currentWidgetData
signal closeAll()
property Component headerComponent: ToolButton {
id: shareToolBt
onClicked: shareWidget(control.title)
text: "\uf1e0"
font.family: fa5solid.name
}
Connections {
target: selector
function onSelectionFinished(index, data) {
onSelectionFinished: {
if(data.cat !== undefined){
updateData({cat: data.cat}, true)
}
@ -74,12 +64,12 @@ DataListView {
delegate: ItemDelegate {
id: partDel
property int thisIndex: index
property int ind: index
property var thisData: widgetData[ "participants" ][index]
state: "closed"
width: control.width
width: parent.width
height: 70
opacity: 0
@ -90,7 +80,7 @@ DataListView {
}
onPressAndHold: {
app.openWidget({person:thisData["PerId"]})
app.openWidget({person:thisData["PerId"]})
}
ParallelAnimation {
@ -101,8 +91,6 @@ DataListView {
text: ""
highlighted: partDel.thisIndex % 2 == 0
onClicked: {
if(state === "closed"){
// close all other delegates
@ -117,11 +105,21 @@ DataListView {
Connections {
target: control
function onCloseAll() {
onCloseAll: {
partDel.state = "closed"
}
}
Rectangle {
anchors.fill: parent
width: partDel.width
color: partDel.ind % 2 == 0 ? "white":"lightgrey"
opacity: 0.2
}
Column {
id: partDelCol
@ -223,7 +221,7 @@ DataListView {
font.pixelSize: Math.abs( height * 0.6 )
minimumPixelSize: 1
text: widgetData[ "participants" ][partDel.thisIndex]["points"] === undefined ? "":"points: "+widgetData[ "participants" ][partDel.thisIndex]["points"]
text: widgetData[ "participants" ][partDel.ind]["points"] === undefined ? "":"points: "+widgetData[ "participants" ][partDel.ind]["points"]
}
}
}
@ -335,7 +333,7 @@ DataListView {
for(var prop in obj) {
// go through the whole array and search for data keys
if (obj.hasOwnProperty(prop) && control.widgetData["participants"][partDel.thisIndex]["result" + prop.replace(" ", "")] !== undefined ) {
if (obj.hasOwnProperty(prop) && control.widgetData["participants"][partDel.ind]["result" + prop.replace(" ", "")] !== undefined ) {
resultData.unshift([prop, obj[prop].replace("\n", " - ")])
//console.log("found " + obj[prop] + " at index " + prop)
}
@ -384,7 +382,7 @@ DataListView {
horizontalAlignment: Text.AlignLeft
minimumPixelSize: 1
text: control.widgetData["participants"][partDel.thisIndex]["result"+detailResultRowRep.compResults[index][0].replace(" ", "")] === undefined ? "":control.widgetData["participants"][partDel.thisIndex]["result"+detailResultRowRep.compResults[index][0].replace(" ", "")].replace("\n", " ")
text: control.widgetData["participants"][partDel.ind]["result"+detailResultRowRep.compResults[index][0].replace(" ", "")] === undefined ? "":control.widgetData["participants"][partDel.ind]["result"+detailResultRowRep.compResults[index][0].replace(" ", "")].replace("\n", " ")
}
Behavior on height {

View file

@ -18,7 +18,6 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.12
import "../Components"
@ -31,34 +30,17 @@ DataListView {
property string subTitle: getSubtitle()
property bool titleIsPageTitle: true
property Component headerComponent: RowLayout {
height: parent.height
spacing: 0
ToolButton {
id: shareToolBt
onClicked: shareWidget(control.title)
text: "\uf1e0"
font.family: fa5solid.name
}
ToolButton {
property Component headerComponent: ToolButton {
id: moreToolBt
onClicked: {
control.changeCat()
}
text: "\uf142"
font.family: fa5solid.name
}
icon.name: "menu"
}
property var widgetData: currentWidgetData
property var athletes
function getSubtitle() {
var titleString
@ -73,16 +55,18 @@ DataListView {
}
}
//% "(Registration)"
var addition = qsTrId("#registrationHeadline")
var addition = "(Registration) "
if(titleString)
if(titleString !== undefined){
return addition + " " + titleString
else
}
else {
return ""
}
}
function getText(athleteData){
function getText(index){
var fedName // federation name
@ -91,20 +75,21 @@ DataListView {
for(var i = 0; i < widgetData["federations"].length; i ++ ){
//console.log("checking " + i + ": cat: " + parseInt(widgetData["categorys"][i]["GrpId"]) + " searched cat: " + root.catId)
if(widgetData["federations"][i]["fed_id"] === athleteData["reg_fed_id"]){
if(widgetData["federations"][i]["fed_id"] === widgetData[ 'athletes' ][index]["reg_fed_id"]){
fedName = widgetData["federations"][i]["shortcut"]
}
}
}
else {
// an international competition -> get nation
fedName = athleteData["nation"]
fedName = widgetData[ 'athletes' ][index]["nation"]
}
return athleteData["firstname"] + " " + athleteData["lastname"] + " (" + fedName + ")"
return widgetData[ "athletes" ][index]["firstname"] + " " + widgetData[ "athletes" ][index]["lastname"] + " (" + fedName + ")"
}
function changeCat() {
function changeCat(){
var cats = control.widgetData["categorys"]
cats.sort(function(a, b) {
@ -121,43 +106,24 @@ DataListView {
}
}
selector.appear(selectOptions, qsTrId("#selectCategory"),
//% "Show results"
"<a href=\"blank\">" + qsTrId("#showResults") + "</a>")
}
function filterAthletes(athletes) {
if(!params.cat) {
params.cat = control.widgetData["categorys"][0]["GrpId"]
}
var filtered = athletes.filter(function(athlete){
var res = String(athlete.cat).toLowerCase() === String(params.cat).toLowerCase()
return res
})
return filtered
selector.appear(selectOptions, qsTr("select cat"))
}
Connections {
target: selector
function onSelectionFinished(index, data) {
onSelectionFinished: {
if(data.cat !== undefined){
updateData({cat: data.cat}, true)
}
}
function onLinkActivated(link) {
selector.close()
var tmpParams = params
tmpParams.type = ""
app.openWidget(tmpParams)
}
}
status: model === 0 ? 901:200
model: widgetData[ 'athletes' ] === undefined ? 0:widgetData[ 'athletes' ].length
Component.onCompleted: {
athletes = filterAthletes(widgetData["athletes"])
if(athletes.length > 0){
if(model > 0){
control.ready = true
control.status = 200
}
@ -171,24 +137,17 @@ DataListView {
updateData({}, false)
}
onWidgetDataChanged: {
athletes = []
athletes = filterAthletes(widgetData["athletes"])
}
model: athletes
delegate: ItemDelegate {
id: partDel
property int thisIndex: index
property var thisData: modelData
property var thisData: widgetData[ "athletes" ][index]
width: parent.width
height: parseInt(thisData.cat) === parseInt(params.cat) ? undefined:0
opacity: 0
scale: 0.9
width: control.width
onThisDataChanged: {
fadeInPa.start()
}
@ -205,20 +164,20 @@ DataListView {
text: ""
highlighted: partDel.thisIndex % 2 == 0
Label {
anchors.fill: parent
anchors.leftMargin: parent.width * 0.05
verticalAlignment: Text.AlignVCenter
fontSizeMode: Text.Fit
font.bold: true
font.pixelSize: Math.abs( height * 0.3 )
font.pixelSize: Math.abs( height * 0.35 )
minimumPixelSize: height * 0.3
elide: "ElideRight"
text: control.getText(partDel.thisData)
text: control.getText(index)
}
}

View file

@ -17,11 +17,13 @@
*/
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Material 2.3
import QtPurchasing 1.12
//import QtLocation 5.13
import QtPurchasing 1.12
import "../Components"
@ -35,44 +37,42 @@ DataListView {
property bool titleIsPageTitle: true
property Component headerComponent: RowLayout {
id: headerComponent
height: parent.height
spacing: 0
layoutDirection: Qt.RightToLeft
ToolButton {
id: moreToolBt
onClicked: control.changeCat()
text: "\uf142"
font.family: fa5solid.name
}
ToolButton {
id: flowToolBt
visible: speedFlowChartPopup.enabled
visible: speedFlowChart.enabled
enabled: control.widgetData['route_order'] === "-1" && Object.keys(control.widgetData['route_names']).length > 2
onClicked: {
control.positionViewAtBeginning()
speedFlowChartPopup.toggle()
if(speedFlowChartBackgroundRect.state === "hidden"){
speedFlowChartBackgroundRect.state ="visible"
control.positionViewAtBeginning()
}
else {
speedFlowChartBackgroundRect.state = "hidden"
}
}
text: "\uf0e8"
font.family: fa5solid.name
icon.name: "flowchart"
}
ToolButton {
id: shareToolBt
id: moreToolBt
onClicked: shareWidget(control.title)
Layout.alignment: Layout.Right
text: "\uf1e0"
font.family: fa5solid.name
onClicked: {
control.changeCat()
}
icon.name: "menu"
}
}
@ -105,34 +105,36 @@ DataListView {
console.log("widget data changed")
if(control.widgetData['discipline'] === 'speed' && control.widgetData['route_order'] === "-1" && Object.keys(control.widgetData['route_names']).length > 2){
speedFlowChartPopup.flowchartData = ({})
speedFlowChartPopup.flowchartData = control.widgetData
speedFlowChartPopup.enabled = true
speedFlowChart.flowchartData = ({})
speedFlowChart.enabled = true
speedFlowChart.flowchartData = control.widgetData
}
else {
speedFlowChartPopup.flowchartData = ({})
speedFlowChartPopup.enabled = false
speedFlowChart.enabled = false
speedFlowChart.flowchartData = ({})
}
}
function onBackRequested() {
if(!speedFlowChartPopup.isVisible())
return true
speedFlowChartPopup.toggle()
return false
}
function getSubtitle() {
var titleString = control.widgetData["route_name"]
var titleString
//% "(Results)"
var addition = qsTrId("#resultsHeadline")
for(var i = 0; i < control.widgetData["categorys"].length; i ++ ){
//console.log("checking " + i + ": cat: " + parseInt(control.widgetData["categorys"][i]["GrpId"]) + " searched cat: " + params.cat)
if(parseInt(control.widgetData["categorys"][i]["GrpId"]) === parseInt(params.cat)){
titleString = control.widgetData["categorys"][i]["name"]
}
}
if(titleString)
var addition = qsTr("(Results)")
if(titleString !== undefined){
return addition + " " + titleString
else
}
else {
return ""
}
}
function changeRoute(route) {
@ -156,12 +158,12 @@ DataListView {
}
}
selector.appear(selectOptions, qsTrId("#selectCategory"))
selector.appear(selectOptions, qsTr("select cat"))
}
Connections {
target: selector
function onSelectionFinished(index, data) {
onSelectionFinished: {
if(data.cat !== undefined){
updateData({cat: data.cat}, true)
}
@ -169,13 +171,454 @@ DataListView {
}
onContentYChanged: {
if(contentY > 0 && speedFlowChartPopup.state === "visible"){
if(contentY > 0 && speedFlowChartBackgroundRect.state === "visible"){
control.positionViewAtBeginning()
}
}
delegate: ResultDelegate {
enabled: speedFlowChartPopup.state === "hidden"
delegate: ItemDelegate {
id: partDel
property int ind: index
property var thisData: widgetData[ "participants" ][partDel.ind]
enabled: speedFlowChartBackgroundRect.state === "hidden"
width: parent.width
height: 70
text: ""
opacity: 0
scale: 0.9
onThisDataChanged: {
fadeInPa.start()
}
onClicked: {
app.openWidget({person:thisData["PerId"]})
}
ParallelAnimation {
id: fadeInPa
NumberAnimation { target: partDel; property: "opacity"; from: 0; to: 1.0; duration: 400 }
NumberAnimation { target: partDel; property: "scale"; from: 0.8; to: 1.0; duration: 400 }
}
Rectangle {
id: partDelBackgroundRect
anchors.fill: parent
width: partDel.width
color: partDel.ind % 2 == 0 ? "white":"lightgrey"
opacity: 0.2
}
Column {
id: partDelCol
anchors.fill: parent
anchors.margins: 5
Row {
id: partDelFirstRow
width: parent.width
height: parent.height - partDelSecondRow.height
Label {
height: parent.height
width: text === "" ? parent.width * 0.08:parent.width * 0.1
fontSizeMode: Text.Fit
font.bold: true
font.pixelSize: Math.abs( partDelSecondRow.height > 0 ? height * 0.6:height * 0.4 )
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: partDel.thisData["result_rank"] === undefined ? "":partDel.thisData["result_rank"]
}
Label {
height: parent.height
width: parent.width * 0.5
fontSizeMode: Text.Fit
font.bold: true
font.pixelSize: Math.abs( partDelSecondRow.height > 0 ? height * 0.6:height * 0.4 )
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
text: partDel.thisData["firstname"] + " " + partDel.thisData["lastname"] + (partDel.thisData["start_number"] !== undefined ? (" (" + partDel.thisData["start_number"] + ")"):"")
}
Label {
height: parent.height
width: parent.width * 0.4
fontSizeMode: Text.Fit
font.bold: false
font.pixelSize: Math.abs( partDelSecondRow.height > 0 ? height * 0.4:height * 0.3 )
minimumPixelSize: height * 0.3 < 1 ? 1:height * 0.3
elide: "ElideRight"
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: "<html>(<a href=\"" + (partDel.thisData["fed_url"] === undefined ? "":partDel.thisData["fed_url"]).toString() + "\">" + (widgetData[ "display_athlete" ] === "nation" ? partDel.thisData["nation"] : partDel.thisData["federation"]) + "</a>)</html>"
onLinkActivated: {
Qt.openUrlExternally(link)
}
}
}
Row {
id: partDelSecondRow
width: parent.width
height: multiResRow.active || multiGenResRow.active || resultLa.acitve ? parent.height / 2 : 0
Row {
id: multiResRow
property bool active: parseInt(widgetData[ "route_order" ]) > -1 && boulderResRep.model > 0
height: parent.height
width: active ? parent.width * 0.75:0
enabled: parseInt(widgetData[ "route_order" ]) > -1 && boulderResRep.model > 0
Repeater {
id: boulderResRep
model: parseInt(widgetData[ "route_num_problems" ])
function getDataForIcon(index){
var resultString = widgetData[ "participants" ][partDel.ind]["boulder"+(index+1)]
var resultList = []
if( resultString !== undefined){
resultString = resultString.replace("t", "")
resultString = resultString.replace("z", "")
resultString = resultString.replace("b", "")
resultList = resultString.split(" ")
while (resultList.length < 2){
resultList.unshift(0)
}
}
else {
resultList = [-1,-1]
}
return resultList
}
delegate: Item {
id: boulderResItm
anchors.verticalCenter: parent.verticalCenter
width: parent.width / ( boulderResRep.model )
height: parent.height
Canvas {
id: boulderResCv
property var resultData: boulderResRep.getDataForIcon(index)
onResultDataChanged: {
boulderResCv.requestPaint()
}
anchors.centerIn: parent
height: parent.height > parent.width ? parent.width * 0.9:parent.height * 0.9
width: height
onPaint: {
var width = 24//boulderResCv.width * 0.9
var height = width
var radius = width * 0.3
var offsetX = width * 0.05
var offsetY = height * 0.05
//console.log("drawing result rect with width: " + width + " and height: " + height)
var context = getContext("2d");
// clear all remainings from other routes
context.clearRect(0, 0, width, height);
context.beginPath();
context.moveTo(0 + offsetX + radius, 0 + offsetY);
// top line
context.lineTo(width - radius + offsetX, 0 + offsetY);
// top right corner
context.arc(width-radius + offsetX, radius + offsetY, radius, 1.5 * Math.PI, 0);
// right line
context.lineTo(width + offsetX, height - radius + offsetY);
// bottom right corner
context.arc(width-radius + offsetX, height - radius + offsetY, radius, 0, 0.5 * Math.PI);
// bottom line
context.lineTo(0 + radius + offsetX, height + offsetY);
// bottom left corner
context.arc(radius + offsetY, height - radius + offsetY, radius, 0.5 * Math.PI, Math.PI);
// left line
context.lineTo(0 + offsetX, radius + offsetY);
// top left corner
context.arc(radius + offsetX, radius + offsetY, radius, Math.PI, 1.5 * Math.PI);
// fill
if(resultData[0] !== -1) {
// if there is a result available -> draw background
context.fillStyle = "#b7b7b7";
}
else {
context.fillStyle = "transparent";
}
context.fill();
// outline
context.lineWidth = 1;
context.strokeStyle = '#424242';
context.stroke();
if(resultData[1] > 0){
// the first triangle
context.beginPath();
// top right corner
context.arc(width-radius + offsetX, radius + offsetY, radius, 1.75 * Math.PI, 0);
// right line
context.lineTo(width + offsetX, height - radius + offsetY);
// bottom right corner
context.arc(width-radius + offsetX, height - radius + offsetY, radius, 0, 0.5 * Math.PI);
// bottom line
context.lineTo(0 + radius + offsetX, height + offsetY);
// bottom left corner
context.arc(radius + offsetX, height - radius + offsetY, radius, 0.5 * Math.PI, 0.75 * Math.PI);
context.closePath();
context.fillStyle = "#44ed38";
context.fill();
// outline
context.lineWidth = 1;
context.strokeStyle = '#424242';
context.stroke();
if(resultData[0] > 0){
// the second triangle
context.beginPath();
// bottom left corner
context.arc(radius + offsetX, height - radius + offsetY, radius, 0.75 * Math.PI, 1 * Math.PI);
// left line
context.lineTo(0 + offsetX, radius + offsetY);
// top left corner
context.arc(radius + offsetX, radius + offsetY, radius, Math.PI, 1.5 * Math.PI);
// top line
context.lineTo(width - radius + offsetX, 0 + offsetY);
// top right corner
context.arc(width-radius + offsetX, radius + offsetY, radius, 1.5 * Math.PI, 1.75 * Math.PI);
context.closePath();
context.fillStyle = "#44ed38";
context.fill();
// outline
context.lineWidth = 1;
context.strokeStyle = '#424242';
context.stroke();
}
}
}
Label {
id: boulderResZoneLa
anchors {
right: parent.right
bottom: parent.bottom
margins: boulderResCv.height * 0.05
}
height: parent.height / 2
width: parent.width / 2
visible: parseInt(text) > 0
fontSizeMode: Text.Fit
font.pixelSize: height
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: boulderResCv.resultData[1]
}
Label {
id: boulderResTopLa
anchors {
left: parent.left
top: parent.top
margins: boulderResCv.height * 0.05
}
height: parent.height / 2
width: parent.width / 2
visible: parseInt(text) > 0
fontSizeMode: Text.Fit
font.pixelSize: height
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: boulderResCv.resultData[0]
}
}
}
}
}
Row {
id: multiGenResRow
property bool active: ((parseInt(widgetData[ "route_order" ]) === -1) && (generalResRep.model > 0)) ? true:false
height: parent.height
width: active ? parent.width - resultLa.width:0
enabled: ((parseInt(widgetData[ "route_order" ]) === -1) && (generalResRep.model > 0)) ? true:false
Repeater {
id: generalResRep
property var routes: getRoutes()
model: routes.length
function getRoutes() {
var obj = widgetData["route_names"]
var routes = []
for(var prop in obj) {
// go through the whole array and search for data keys
if (obj.hasOwnProperty(prop) && prop > -1) {
routes.push([prop, obj[prop]])
//console.log("found " + obj[prop] + " at index " + prop)
}
}
routes.sort(function(a, b) {
return a[0] - b[0];
});
return routes
}
delegate: Item {
id: boulderGenResItm
anchors.verticalCenter: parent.verticalCenter
width: parent.width / ( generalResRep.model )
height: parent.height
visible: multiGenResRow.active
Rectangle {
anchors {
left: parent.left
}
width: 1
height: parent.height
visible: index === 0
color: "grey"
}
Rectangle {
anchors {
right: parent.right
}
width: 1
height: parent.height
color: "grey"
}
Label {
id: boulderGenResLa
anchors.centerIn: parent
height: parent.height
width: parent.width * 0.9
fontSizeMode: Text.Fit
font.pixelSize: Math.abs( height * 0.6 )
minimumPixelSize: 1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: widgetData[ "participants" ][partDel.ind]["result"+(generalResRep.routes[index][0])] === undefined ? "":widgetData[ "participants" ][partDel.ind]["result"+(generalResRep.routes[index][0])]
}
}
}
}
Label {
id: resultLa
property bool acitve: ( boulderResRep.model > 0 || widgetData["discipline"] !== "boulder" ) && parseInt(widgetData[ "route_order" ]) > -1
width: enabled ? parent.width * 0.25:0
height: enabled ? parent.height:0
enabled: ( boulderResRep.model > 0 || widgetData["discipline"] !== "boulder" ) && parseInt(widgetData[ "route_order" ]) > -1
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
fontSizeMode: Text.Fit
font.pixelSize: Math.abs( height * 0.6 )
minimumPixelSize: 1
text: widgetData[ "participants" ][partDel.ind]["result"] === undefined ? "":widgetData[ "participants" ][partDel.ind]["result"]
}
}
}
}
footer: ItemDelegate {
@ -197,7 +640,7 @@ DataListView {
property var tabs: getTabs()
property var tabIndexes: []
enabled: speedFlowChartPopup.state === "hidden"
enabled: speedFlowChartBackgroundRect.state === "hidden"
anchors {
left: parent.left
@ -277,13 +720,195 @@ DataListView {
}
SpeedFlowChartPopup {
id: speedFlowChartPopup
Rectangle {
id: speedFlowChartBackgroundRect
state: "hidden"
anchors {
top: parent.top
bottom: parent.bottom
left: parent.right
}
width: parent.width
height: parent.height
color: Material.background
SpeedFlowChart {
id: speedFlowChart
anchors.fill: parent
enabled: false
flowchartData: ({})
onEnabledChanged: {
if(!enabled){
speedFlowChartBackgroundRect.state = 'hidden'
}
}
Rectangle {
id: speedFlowChartLockedOverlay
state: appSettings.read("speedBackendPurchase") === "1" ? "unlocked":"locked"
anchors.fill: parent
anchors.margins: -20
color: "white"
Connections {
target: speedFlowChartProduct
onPurchaseRestored: {
speedFlowChartLockedOverlay.state = appSettings.read("speedBackendPurchase") === "1" ? "unlocked":"locked"
}
onPurchaseSucceeded: {
speedFlowChartLockedOverlay.state = appSettings.read("speedBackendPurchase") === "1" ? "unlocked":"locked"
}
onPurchaseFailed: {
purchaseBt.text = qsTr("Purchase failed")
purchaseBt.enabled = false
buttonTextResetTimer.start()
}
}
Timer {
id: buttonTextResetTimer
interval: 2000
running: false
repeat: false
onTriggered: {
purchaseBt.text = (speedFlowChartProduct.status === Product.Registered
? "Buy now for " + speedFlowChartProduct.price
: qsTr("this item is currently unavailable"))
purchaseBt.enabled = true
}
}
ColumnLayout {
id: lockedLayout
anchors {
fill: parent
topMargin: parent.height * 0.05
bottomMargin: parent.height * 0.1
rightMargin: parent.width * 0.1 + 20
leftMargin: parent.width * 0.1 + 20
}
//spacing: parent.height * 0.05
Image {
id: name
Layout.alignment: Layout.Center
Layout.preferredHeight: height
Layout.preferredWidth: width
width: lockedLayout.height * 0.1
height: width
mipmap: true
source: "qrc:/icons/lock.png"
}
Text {
Layout.fillWidth: true
height: parent.height * 0.05
text: qsTr("This is a premium feature.")
font.bold: true
font.pixelSize: parent.height * 0.05
fontSizeMode: Text.Fit
minimumPixelSize: 1
horizontalAlignment: Text.AlignHCenter
}
SwipeGallery {
Layout.fillHeight: true
Layout.fillWidth: true
images: ["qrc:/screenshots/SpeedFlowchartDemo/1.png","qrc:/screenshots/SpeedFlowchartDemo/2.png","qrc:/screenshots/SpeedFlowchartDemo/3.png"]
}
Button {
id: purchaseBt
Layout.alignment: Layout.Center
enabled: speedFlowChartProduct.status === Product.Registered
text: speedFlowChartProduct.status === Product.Registered
? "Buy now for " + speedFlowChartProduct.price
: qsTr("this item is currently unavailable")
icon.name: "buy"
//display: AbstractButton.TextBesideIcon
onClicked: speedFlowChartProduct.purchase()
}
}
states: [
State {
name: "unlocked"
PropertyChanges {
target: speedFlowChartLockedOverlay
visible: false
}
},
State {
name: "locked"
PropertyChanges {
target: speedFlowChartLockedOverlay
visible: true
}
}
]
}
}
states: [
State {
name: "hidden"
PropertyChanges {
target: speedFlowChartBackgroundRect
opacity: 0
anchors.leftMargin: 0
}
},
State {
name: "visible"
PropertyChanges {
target: speedFlowChartBackgroundRect
opacity: 1
anchors.leftMargin: -parent.width
}
}
]
transitions: [
Transition {
NumberAnimation {
properties: "opacity,scale,anchors.leftMargin"
duration: 200
easing.type: Easing.InOutQuad
}
}
]
}
PullRefresher {
// has to be placed here again,
// to be on top of the speed flowchart
target: control
postRefreshDelay: 0
@ -296,5 +921,4 @@ DataListView {
}
}
}

View file

@ -19,7 +19,6 @@
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.12
import "../Components"
@ -29,45 +28,32 @@ DataListView {
property bool ready
property string title: control.widgetData['comp_name']
property string subTitle: getSubtitle()
property string subTitle: qsTr("(Startlist)") + " " + control.widgetData['route_name'] //getSubtitle()
property bool titleIsPageTitle: true
property Component headerComponent: RowLayout {
height: parent.height
spacing: 0
ToolButton {
id: shareToolBt
onClicked: shareWidget(control.title)
text: "\uf1e0"
font.family: fa5solid.name
}
ToolButton {
property Component headerComponent: ToolButton {
id: moreToolBt
onClicked: {
control.changeCat()
}
text: "\uf142"
font.family: fa5solid.name
}
icon.name: "menu"
}
function getSubtitle() {
var titleString = control.widgetData["route_name"]
var titleString
//% "(Startlist)"
var addition = qsTrId("#startlistHeadline")
for(var i = 0; i < control.widgetData["categorys"].length; i ++ ){
//console.log("checking " + i + ": cat: " + parseInt(control.widgetData["categorys"][i]["GrpId"]) + " searched cat: " + params.cat)
if(parseInt(control.widgetData["categorys"][i]["GrpId"]) === parseInt(params.cat)){
titleString = control.widgetData["categorys"][i]["name"]
}
}
var addition = qsTr("(Startlist)")
return addition + " " + titleString
if(titleString)
return addition + " " + titleString
else
return ""
}
function changeRoute(route) {
@ -91,12 +77,12 @@ DataListView {
}
}
selector.appear(selectOptions, qsTrId("#selectCategory"))
selector.appear(selectOptions, qsTr("select cat"))
}
Connections {
target: selector
function onSelectionFinished(index, data) {
onSelectionFinished: {
if(data.cat !== undefined){
updateData({cat: data.cat}, true)
}
@ -127,11 +113,9 @@ DataListView {
delegate: ItemDelegate {
id: partDel
property int thisIndex: index
property var thisData: widgetData[ "participants" ][index]
width: control.width
height: 50
width: parent.width
opacity: 0
scale: 0.9
@ -152,8 +136,6 @@ DataListView {
text: ""
highlighted: partDel.thisIndex % 2 == 0
Row {
id: partDelFirstRow
@ -163,7 +145,7 @@ DataListView {
rightMargin: parent.width * 0.05
}
spacing: width * 0.05
spacing: width * 0.1
Label {
height: parent.height
@ -171,7 +153,7 @@ DataListView {
fontSizeMode: Text.Fit
font.bold: true
font.pixelSize: Math.abs( height * 0.4 )
font.pixelSize: Math.abs( height * 0.6 )
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
@ -180,7 +162,7 @@ DataListView {
Label {
height: parent.height
width: parent.width * 0.45
width: parent.width * 0.4
fontSizeMode: Text.Fit
font.bold: true
@ -196,8 +178,10 @@ DataListView {
height: parent.height
width: parent.width * 0.3
fontSizeMode: Text.Fit
font.bold: false
font.pixelSize: height * 0.3
font.pixelSize: Math.abs( height * 0.4 )
minimumPixelSize: height * 0.3
elide: "ElideRight"

View file

@ -20,198 +20,49 @@ import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.12
import QtPurchasing 1.12
import de.itsblue.blueROCK 1.0
import com.itsblue.digitalRockRanking 1.0
import de.itsblue.blueRock 2.0
import "./Pages"
import "./Components"
import "./Widgets"
Window {
visible: true
width: 540
height: 960
title: "blueROCK"
title: qsTr("blueROCK")
Page {
id: app
property int errorCode: -1
property var colorShade: Material.theme === Material.Light ? Material.Shade300 : Material.Shade700
property color nationalAdultsColor: Material.color(Material.Green, colorShade)
property color nationalYouthColor: Material.color(Material.LightGreen, colorShade)
property color federalColor: Material.color(Material.Grey, colorShade)
// comp cats source:
// - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/dav_calendar.php
// - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/sac_calendar.php
property var compCats: {
// --- ICC ---
/*'int' : {
'label' : 'International',
'nation' : 'ICC',
'wettk_reg' : '^[0-9]{2,2}[_^E]{1}[^YJ]{1,1}.*',
'serie_reg' : '^[0-9]{2,2}_(WC|TR){1,1}.*',
'rang_title': 'CUWR continuously updated WORLDRANKING',
'bgcolor' : '#B8C8FF',
'nat_team_ranking' : '',
'cat_id' : [68,86]//[68,69,70,86,259]
},*/
'worldcup': {
'label' : 'World Cups',
'nation' : 'ICC',
'bgcolor' : '#B8C8FF',
'sort_rank': 1,
'cat_id' : [69]
},
'youth' : {
'label' : 'Youth Events',
'nation' : 'ICC',
'wettk_reg' : '^[0-9]{2,2}(EYC|_J|_Y){1,1}.*',
'serie_reg' : '^[0-9]{2,2}_EYC',
'rang_title': '',
'bgcolor' : '#D8E8FF',
'sort_rank': 2,
'cat_id' : [71,258]
},
'cont': {
'label' : 'Continental Events',
'nation' : 'ICC',
'bgcolor' : '#B8C8FF',
'sort_rank': 3,
'cat_id' : [262]
},
'masters' : {
'label' : 'Masters and Promo Events',
'nation' : 'ICC',
'wettk_reg' : '^[0-9]{2,2}_[^PWERASL]{1}.*',
// 'serie_reg' : '^[0-9]{2,2}_(WC|TR){1,1}.*',
// 'rang_title': 'CUWR continuously updated WORLDRANKING',
'bgcolor' : '#F0F0F0',
'sort_rank': 4,
'cat_id' : [70]
},
'para' : {
'label' : 'Paraclimbing Events',
'nation' : 'ICC',
'wettk_reg' : '^[0-9]{2,2}_PE.*',
'bgcolor' : '#F0F0F0',
'sort_rank': 5,
'cat_id' : [256,259]
},
'games': {
'label': 'Games',
'nation': 'ICC',
'bgcolor' : '#B8C8FF',
'sort_rank': 6,
'cat_id': [68,86]
},
// --- GER ---
'ger_meisterschaft' : {
'label' : 'Deutsche Meisterschaft',
'nation' : 'GER',
'bgcolor' : app.nationalAdultsColor,//'#A8F0A8',
'sort_rank': 1,
'cat_id' : [57, 59, 60]
},
'ger_jugend' : {
'label' : 'Deutscher Jugendcup',
'nation' : 'GER',
'wettk_reg' : '^[0-9]{2,2}[_J]{1,1}[^WL]+.*',
'serie_reg' : '^[0-9]{2,2}_JC',
// 'rang_title': 'Deutsche Jugend RANGLISTE',
'bgcolor' : app.nationalYouthColor,//'#D8FFD8',
'sort_rank': 2,
'cat_id' : [58]
},
'ger_state' : {
'label' : 'Landesmeisterschaft',
'nation' : 'GER',
'wettk_reg' : '^[0-9]{2,2}[_J]{1,1}LM.*',
'serie_reg' : '^[0-9]{2,2}[_J]{1,1}LM.*',
'rang_title': '',
'bgcolor' : app.federalColor, //'#F0F0F0',
'sort_rank': 3,
'cat_id' : [61,56]
},
// --- SUI ---
'sui' : {
'label' : 'Erwachsene',
'nation' : 'SUI',
'wettk_reg' : '^[0-9]{2,2}_[^R].*',
'serie_reg' : '.*',
'rang_title': 'SWISS RANKING',
'bgcolor' : app.nationalAdultsColor, //'#A8F0A8',
'sort_rank': 1,
'cat_id' : [62,63]
},
'sui_jugend' : {
'label' : 'Jugend',
'nation' : 'SUI',
'wettk_reg' : '^[0-9]{2,2}_[^R].*',
'serie_reg' : '.*',
'rang_title': 'SWISS RANKING',
'bgcolor' : app.nationalYouthColor, //'#D8FFD8',
'sort_rank': 2,
'cat_id' : [65]
},
'sui_local' : {
'label' : 'RegioCups',
'nation' : 'SUI',
'wettk_reg' : '^[0-9]{2,2}_RG_.*',
'rang_title': '',
'bgcolor' : app.federalColor, //'#F0F0F0',
'sort_rank': 3,
'cat_id' : [64]
},
'sui_ice' : {
'label' : 'Iceclimbing',
'nation' : 'SUI',
'wparams["valid"]ettk_reg' : '^[0-9]{2,2}_RC_.*',
'rang_title': '',
'bgcolor' : app.federalColor, //'#F0F0F0',
'sort_rank': 4,
'cat_id' : [84]
}
}
anchors.fill: parent
Material.theme: appSettings.read("darkTheme") === "true" ? Material.Dark:Material.Light
FontLoader {
id: fa5solid
source: "qrc:/fonts/fa5solid.otf"
}
FontLoader {
id: fa5regular
source: "qrc:/fonts/fa5regular.otf"
Component.onCompleted: {
//app.openAthlete(53139) // dorian: 53139 , rustam: 6933 , helen: 53300
//openWidget({nation:'GER'})
//mainStack.push("Pages/AthleteSearchPage.qml")
}
Shortcut {
sequences: ["Esc", "Back"]
enabled: mainStack.depth > 1
onActivated: app.goBack()
onActivated: {
if(!mainStack.currentItem.locked){
mainStack.pop()
}
}
}
BlueRockBackend {
id: serverConn
BRController {
id: brController
}
onOpenedViaUrl: {
app.openWidgetFromUrl(url)
}
ServerConn {
id: serverConn
}
AppSettings {
@ -221,6 +72,8 @@ Window {
StackView {
id: mainStack
//enabled: !loadingDl.opened
anchors {
top: toolBar.bottom
left: parent.left
@ -288,7 +141,8 @@ Window {
RowLayout {
anchors.fill: parent
width: toolBar.width
height: toolBar.height
spacing: width * 0.02
@ -297,29 +151,37 @@ Window {
height: parent.height
onClicked: app.goBack()
onClicked: {
if(!mainStack.currentItem.locked){
mainStack.pop()
}
}
text: "\uf053"
font.family: fa5solid.name
icon.name: "back"
}
Column {
Layout.fillWidth: true
Layout.alignment: Layout.Center
height: childrenRect.height
width: parent.width - extraComponentLoader.width - toolButton.width - 3 * parent.spacing
//width: parent.width - extraComponentLoader.width - toolButton.width - 3 * parent.spacing
MovingLabel {
Label {
id: toolBarTitleLa
syncWithLabel: toolBarSubTitleLa
width: parent.width
scale: 1
elide: "ElideRight"
font.bold: true
verticalAlignment: Text.AlignVCenter
color: "black"
text: getText()
function getText(){
@ -348,16 +210,18 @@ Window {
}
}
MovingLabel {
Label {
id: toolBarSubTitleLa
visible: text !== ""
syncWithLabel: toolBarTitleLa
width: parent.width
height: text !== "" ? undefined:0
elide: "ElideRight"
font.bold: false
color: "black"
text: getText()
function getText(){
@ -367,7 +231,7 @@ Window {
titleString = mainStack.currentItem.subTitle
}
return titleString
return(titleString)
}
Behavior on text {
@ -386,17 +250,26 @@ Window {
height: parent.height
onItemChanged: {
if(item === null) {
extraComponentLoader.Layout.preferredWidth = 0
return
}
Layout.alignment: Layout.Center
extraComponentLoader.item.width = extraComponentLoader.item.implicitWidth
extraComponentLoader.Layout.preferredWidth = extraComponentLoader.item.width
widthChangedCon.target = extraComponentLoader.item
active: true
onStatusChanged: {
console.log("LOADER status changed: " + status)
if(status === Loader.Ready){
console.log("set loader width to: " + extraComponentLoader.Layout.preferredWidth + " for a item width of: " + extraComponentLoader.item.implicitWidth)
extraComponentLoader.item.width = extraComponentLoader.item.implicitWidth
extraComponentLoader.Layout.preferredWidth = extraComponentLoader.item.width
widthChangedCon.target = extraComponentLoader.item
}
else {
extraComponentLoader.Layout.preferredWidth = 0
}
}
//sourceComponent: mainStack.currentItem.headerComponent
Connections {
target: mainStack
function onCurrentItemChanged() {
@ -407,7 +280,6 @@ Window {
Connections {
id: secondCon
target: null
function onHeaderComponentChanged() {
disappearNa.start()
}
@ -474,6 +346,9 @@ Window {
onRunningChanged: {
if(!running){
console.log("SETTING HEADER COMP")
extraComponentLoader.Layout.preferredWidth = 0
extraComponentLoader.sourceComponent = null
extraComponentLoader.sourceComponent = mainStack.currentItem.headerComponent
appearNa.start()
}
@ -511,7 +386,7 @@ Window {
]
}
Popup {
Dialog {
id: loadingDl
x: ( app.width - width ) / 2
@ -521,9 +396,6 @@ Window {
closePolicy: Dialog.NoAutoClose
contentItem: Column {
spacing: 50
FancyBusyIndicator {
running: true
}
@ -533,30 +405,16 @@ Window {
anchors.horizontalCenter: parent.horizontalCenter
font.bold: true
color: "white"
//% "Loading"
text: qsTrId("#loading") + "..."
text: "loading..."
}
}
background: Rectangle {
color: "transparent"
}
}
Store {
id: inAppProductStore
Product {
id: speedFlowChartProduct
identifier: "speed_flowchart"
//identifier: "android.test.purchased"
//identifier: "android.test.canceled"
//identifier: "android.test.refunded"
type: Product.Unlockable
onPurchaseRestored: {
@ -573,58 +431,65 @@ Window {
}
}
function landscape() {
function landscape(){
return app.height < app.width
}
function toggleDarkMode() {
var dark = app.Material.theme === Material.Light
app.Material.theme = dark ? Material.Dark : Material.Light
appSettings.write("darkTheme", dark)
}
function largeScreen() {
return Math.min(app.width, app.height) > 750
}
function openWidget(params) {
function openWidget(params){
loadingDl.open()
console.log("Opening widget: ", JSON.stringify(params))
console.log("OPENING COMP 2")
var calComp = Qt.createComponent("qrc:/Pages/WidgetPage.qml").createObject(null, {"params": params})
console.log("OPENING COMP 3")
app.errorCode = calComp.status
var result = false
if(Object.keys(params).length) {
var calComp = Qt.createComponent("qrc:/Pages/WidgetPage.qml").createObject(null, {"params": params})
app.errorCode = calComp.status
if(calComp.ready) {
mainStack.push(calComp)
result = true
}
else {
delete(calComp)
}
if(calComp.ready){
mainStack.push(calComp)
}
else {
delete(calComp)
}
loadingDl.close()
return result
}
function openWidgetFromUrl(url) {
var result = serverConn.getParamsFromUrl(url)
function openCalendar(federation) {
loadingDl.open()
var newPageComp = Qt.createComponent("qrc:/Pages/CalendarPage.qml").createObject(null, {"data": brController.getCalendar(federation)})
if(result["valid"]) {
openWidget(result["params"])
return app.errorCode !== 906
app.errorCode = newPageComp.status
if(newPageComp.ready){
mainStack.push(newPageComp)
}
else {
delete(newPageComp)
}
return result["valid"]
loadingDl.close()
}
function defaultString(string, defaultString) {
if(string === undefined || string === null) {
function openResults(competition) {
loadingDl.open()
console.log("OPENING COMP 2")
var newPageComp = Qt.createComponent("qrc:/Pages/ResultPage.qml").createObject(null, {"data": competition})
console.log("OPENING COMP 3")
app.errorCode = newPageComp.status
if(newPageComp.ready){
mainStack.push(newPageComp)
}
else {
delete(newPageComp)
}
loadingDl.close()
}
function defaultString(string, defaultString){
if(string === undefined || string === null){
return defaultString
}
else {
@ -632,11 +497,6 @@ Window {
}
}
function goBack() {
if(!mainStack.currentItem.hasOwnProperty('onBackRequested') || mainStack.currentItem.onBackRequested())
mainStack.pop()
}
function getErrorInfo(errorCode) {
var infoLevel
@ -645,36 +505,66 @@ Window {
// 2 - error
var errorString
var errorDescription
switch(errorCode) {
case 0:
infoLevel = 2
//% "No connection to server"
errorString = qsTrId("#noConnectionError")
errorString = "No connection to server"
errorDescription = "Please check your internet connection and try again."
break
case 404:
case 200:
infoLevel = 0
errorString = "Success"
errorDescription = "The request was successfull"
break
case 401:
infoLevel = 2
//% "Not found"
errorString = qsTrId("#notFoundError")
errorString = "Authentication required"
errorDescription = "The server asked for user credentinals, please chack them and try again"
break
case 500:
infoLevel = 2
errorString = "Internal server error"
errorDescription = "The server was unable to process this request, this is probaply the servers fault. Please try again later."
break
case 900:
infoLevel = 2
errorString = "Internal error"
errorDescription = "Something went wron internally, this is probaply an inssue in the program code"
break
case 901:
infoLevel = 1
//% "No Data"
errorString = qsTrId("#noDataError")
errorString = "No Data"
errorDescription = "There is currently no data available. Please try again later."
break
case 906:
case 902:
infoLevel = 1
errorString = "Cached (old) data"
errorDescription = "Es konnte keine Verbindung zum Server hergestellt werden, aber es sind noch alte Daten gespeichert."
break
case 903:
infoLevel = 1
errorString = "Ungültiger Aufruf"
errorDescription = "Die aufgerufene Funktion ist momentan nicht verfügbar, bitte versuche es später erneut."
break
case 904:
infoLevel = 2
//% "Invalid Request"
errorString = qsTrId("#invalidRequestError")
errorDescription = "Invalid Request"
errorString = "Incompatible API"
errorDescription = "Please make shure that you are using the latest version of this app and try again."
break
case 905:
infoLevel = 1
errorString = "Loading..."
errorDescription = "Please wait while we're loading some data"
break
default:
infoLevel = 2
//% "Unexpected error"
errorString = qsTrId("#unexpectedError") + " ("+errorCode+")"
errorString = "Unexpected error ("+errorCode+")"
errorDescription = "Unexpected error while getting data from the server. Please try again later."
}
return([infoLevel, errorString])
return([infoLevel, errorString, errorDescription])
}
}

View file

@ -19,21 +19,9 @@
<file>Pages/AthleteSearchPage.qml</file>
<file>Components/SpeedFlowChart.qml</file>
<file>Components/SwipeGallery.qml</file>
<file>Components/CompetitionCalendarDelegate.qml</file>
<file>Pages/CalendarPage.qml</file>
<file>Pages/BRWidgetPage.qml</file>
<file>Pages/ResultPage.qml</file>
<file>Components/SelectorPopup.qml</file>
<file>Components/ResultDelegate.qml</file>
<file>Components/SpeedFlowChartLocker.qml</file>
<file>Components/SpeedFlowChartPopup.qml</file>
<file>Components/BlueRockBadge.qml</file>
<file>Components/DisclaimerDialog.qml</file>
<file>Components/ColoredItemDelegate.qml</file>
<file>Components/AlignedButton.qml</file>
<file>Components/SharePopup.qml</file>
<file>Pages/QrCodeScanPage.qml</file>
<file>Components/MovingLabel.qml</file>
<file>Components/SpeedFlowChart.js</file>
<file>Components/BoulderResultRow.qml</file>
<file>Components/GeneralResultRow.qml</file>
<file>Components/SpeedQualificationResultRow.qml</file>
</qresource>
</RCC>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more