Compare commits

...

94 commits

Author SHA1 Message Date
Dorian Zedler bf617ba72c
Chore: don't show 0.000 in speed results
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-15 10:15:15 +02:00
Dorian Zedler 5694d61c4b
Feat: show route_quota 2023-07-15 10:11:26 +02:00
Dorian Zedler cd0500c40b
Feat: show both results in speed quali 2023-07-05 16:27:15 +02:00
Dorian Zedler 12185337b9
Merge branch 'main' of ssh://itsblue.dev/blueROCK/app 2023-05-14 22:56:33 +02:00
Dorian Zedler fe453cd5ac
Fix: fix for new Android API 2023-05-14 22:55:49 +02:00
Dorian Zedler 6644d16a92 Chore: update qt-andrioid version
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-02-06 08:57:07 +01:00
Dorian Zedler f42d8fb8d7
Chore: update changelog
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2022-10-20 06:42:18 +02:00
Dorian Zedler e6569694e4
Chore: update permissions 2022-10-20 06:41:37 +02:00
Dorian Zedler 38dbbb3b32 Merge pull request 'Fix: release CI' (#38) from fix/release-ci into main
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/tag Build is passing
continuous-integration/drone Build is passing
Reviewed-on: #38
2022-08-14 18:08:56 +02:00
Dorian Zedler ce7fc3d367 Fix: release CI
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/pr Build was killed
2022-08-14 18:08:42 +02:00
Dorian Zedler 8e4e135f9c Merge pull request 'release: 0.7.0' (#37) from release/0.7.0 into main
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/tag Build is passing
Reviewed-on: #37
2022-08-14 17:55:44 +02:00
Dorian Zedler c13d108cff
Chore: update release date
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/pr Build was killed
2022-08-14 17:55:26 +02:00
Dorian Zedler 367807b90d
Chore: prepare release 0.7.0
Some checks are pending
continuous-integration/drone/push Build is running
continuous-integration/drone/pr Build is running
2022-08-14 17:53:56 +02:00
Dorian Zedler 6ca5b81f6d
Fix: Font size on Android 2022-08-14 17:53:35 +02:00
Dorian Zedler 05bce5e97d Merge pull request 'Chore: refactor speed flowchart (fixes #26)' (#36) from fix/speed-flowchart into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #36
2022-08-14 17:50:58 +02:00
Dorian Zedler a238a68638
Chore: remove log
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-08-14 17:46:19 +02:00
Dorian Zedler 6d7009b183
Chore: refactor speed flowchart (fixes #26)
Some checks are pending
continuous-integration/drone/push Build is running
continuous-integration/drone/pr Build is running
2022-08-14 17:43:38 +02:00
Dorian Zedler 0ca2725bf3 Merge pull request 'Fix: send file on android api 30 (fixes #32)' (#33) from fix/share-on-android-api-30 into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #33
2022-08-11 12:49:41 +02:00
Dorian Zedler 447c1135dd
Fix: send file on android api 30 (fixes #32)
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-08-11 12:44:39 +02:00
Dorian Zedler 38bdf15f40 Merge pull request 'Feat: add start numbers to speed flowchart (fixes #30)' (#31) from feat/startnumbers-in-speed-tree into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #31
2022-08-11 12:15:17 +02:00
Dorian Zedler 0920ee6f77
Fix: don't go to comp on start
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-08-11 08:51:05 +02:00
Dorian Zedler a1ea8a4c65
Fix: don't unlock speed flowchart
Some checks reported errors
continuous-integration/drone/push Build is running
continuous-integration/drone/pr Build was killed
2022-08-11 08:49:37 +02:00
Dorian Zedler 21c479cd55
Feat: add start numbers to speed flowchart (fixes #30)
Some checks reported errors
continuous-integration/drone/push Build is running
continuous-integration/drone/pr Build was killed
2022-08-11 08:47:22 +02:00
Dorian Zedler 766da23e26
Merge branch 'main' of ssh://itsblue.dev/blueROCK/app
All checks were successful
continuous-integration/drone/push Build is passing
2022-08-10 09:32:35 +02:00
Dorian Zedler 496d66a521
Chore: update screenshots 2022-08-10 09:32:11 +02:00
Dorian Zedler 43d98bd43e Update '.drone.yml'
All checks were successful
continuous-integration/drone/push Build is passing
2022-08-08 23:48:26 +02:00
Dorian Zedler 30471725d0 Update '.drone.yml'
Some checks are pending
continuous-integration/drone/push Build is running
2022-08-08 23:48:09 +02:00
Dorian Zedler 88ebde27f2
Chore: update version and changelog
All checks were successful
continuous-integration/drone/push Build is passing
2022-08-06 22:00:37 +02:00
Dorian Zedler 4bf2e2b3b3
Merge branch 'master' of ssh://itsblue.dev/blueROCK/app
Some checks are pending
continuous-integration/drone/push Build is running
2022-08-06 21:56:17 +02:00
Dorian Zedler d234474233
Chore: remove app.bluerock.dev from domains 2022-08-06 21:55:59 +02:00
Dorian Zedler 78659fe769 Update '.drone.yml'
All checks were successful
continuous-integration/drone/push Build is passing
2022-08-03 12:52:55 +02:00
Dorian Zedler 99f49e0798
Merge branch 'master' of ssh://itsblue.dev/blueROCK/app
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-08-03 12:52:08 +02:00
Dorian Zedler 455c4044c2
Merge branch 'getcommit' 2022-08-03 12:51:57 +02:00
Dorian Zedler ce67df3416
Chore: update openssl lib path 2022-08-03 12:50:15 +02:00
Dorian Zedler 53460b2091
Add fastlane and ressources 2022-08-01 14:30:57 +02:00
Dorian Zedler 2f3f0488da Update '.drone.yml'
All checks were successful
continuous-integration/drone/push Build is passing
2022-08-01 12:00:43 +02:00
Dorian Zedler a026912929 Update '.drone.yml'
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-30 14:35:59 +02:00
Dorian Zedler 551c6ffb6d Update '.drone.yml'
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-30 14:22:04 +02:00
Dorian Zedler 55a5aac4e1 Update '.drone.yml'
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-07-30 13:52:34 +02:00
Dorian Zedler 21b88575fb Update '.drone.yml'
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-29 14:40:16 +02:00
Dorian Zedler fb17ec52cd Update '.drone.yml'
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-29 14:35:31 +02:00
Dorian Zedler 6b91988ac5 Update '.drone.yml'
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-29 14:30:09 +02:00
Dorian Zedler 75d7212fd9 Update '.drone.yml'
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2022-07-29 14:29:52 +02:00
Dorian Zedler 3a735e2cf4 Update '.drone.yml'
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2022-07-29 14:29:01 +02:00
Dorian Zedler c92362f07a Update '.drone.yml'
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2022-07-29 14:26:45 +02:00
Dorian Zedler 3492e6c511 Update '.drone.yml'
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-29 14:19:01 +02:00
Dorian Zedler 5cfad624a3 Update '.drone.yml'
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-29 14:09:49 +02:00
Dorian Zedler dabb2473b3
Feat: Add fastlande stuff 2022-07-29 09:23:41 +02:00
Dorian Zedler d33748a06d Update '.drone.yml'
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-29 09:06:39 +02:00
Dorian Zedler 1cd7ad6649 Update '.drone.yml'
Some checks failed
continuous-integration/drone/push Build is failing
2022-07-29 08:49:02 +02:00
Dorian Zedler d881b203fa Add '.drone.yml'
Some checks reported errors
continuous-integration/drone Build encountered an error
2022-07-29 08:48:09 +02:00
Dorian Zedler 175c2ea264
Merge branch 'version/0.5.1' 2021-10-20 21:41:28 +02:00
Dorian Zedler 484fb8d7a9
Bump version 2021-10-20 21:35:47 +02:00
Dorian Zedler 7fbf90b215
Add privacy policy link 2021-10-20 20:21:45 +02:00
Dorian Zedler 767556c083
Release 0.6.0 on Android 2021-08-07 13:14:05 +02:00
Dorian Zedler 9af65bd746
- Fix selector popup clipping
- Fix rare issue with backgound of boulder result rect
2021-08-06 21:39:29 +02:00
Dorian Zedler 1ce24be35b
Fix poster size; It is now A4 2021-07-08 15:34:40 +02:00
Dorian Zedler acd436f000
- show route name instead of category name in subtitle
- reset scroll of synceed label when scroll of moving label is being reset
2021-07-08 12:23:55 +02:00
Dorian Zedler 9c979d43ab
Some size changes for comp name on poster 2021-07-08 11:42:31 +02:00
Dorian Zedler 45fd7add57
Some size and translation fixes 2021-07-08 00:46:20 +02:00
Dorian Zedler 74403dd990
Hugely improove the poster template 2021-07-08 00:28:32 +02:00
Dorian Zedler a2e6cdccb9
Version 0.5.1 2021-07-06 09:17:05 +02:00
Dorian Zedler f1d6915c52 Merge branch 'fix/in-appPurchase' into 'master'
Fix in-app purchase

Closes #25

See merge request dorian/blueROCK!2
2021-07-06 07:12:44 +00:00
Dorian Zedler f422aecb77
Fix in-app purchase 2021-07-06 09:11:15 +02:00
Dorian Zedler 800796fb06
- more translations
- some fixes
2021-07-05 20:55:34 +02:00
Dorian Zedler 7d6b972ef3
Fix compile issue 2021-06-29 17:51:29 +02:00
Dorian Zedler 0d05027086
Merge branch 'version/0.5.1' of ssh://ssh.itsblue.dev:10040/dorian/blueROCK into version/0.5.1 2021-06-29 17:47:57 +02:00
Dorian Zedler d286a87553
Add translation 2021-06-29 17:47:39 +02:00
Dorian Zedler 20ffca4670
- Fixed registration widget issues
- Added share button to registration, startlist and profile widget
2021-06-29 17:44:35 +02:00
Dorian Zedler 38ad92207e Implement sharing on IOS 2021-06-25 23:22:28 +02:00
Dorian Zedler cc855bd329
Optimizations in QrCodePage 2021-06-25 09:41:07 +02:00
Dorian Zedler 5a50ef8cbe
Started to implement propper permission handling 2021-06-21 16:03:42 +02:00
Dorian Zedler 9ca38f8651
Add some translations 2021-06-20 21:08:59 +02:00
Dorian Zedler 6594304deb
Started to make strings translatable 2021-06-20 20:11:19 +02:00
Dorian Zedler 30a29003a9
re-add ifsc cats just for fun 2021-06-20 19:38:50 +02:00
Dorian Zedler 275fff2d22
Add propper link handling 2021-06-20 19:04:36 +02:00
Dorian Zedler 0b8c253564
- fix sharing callback
- some more minor adjustments
2021-06-20 16:51:31 +02:00
Dorian Zedler 06e76bc287
Implement Qr-code generation and scanning 2021-06-20 15:09:53 +02:00
Dorian Zedler 8d1f0173ed
Add QZXing as submodule 2021-06-19 09:14:09 +02:00
Dorian Zedler 28400f98e4
Start addind share utils and pds generation 2021-06-19 09:06:52 +02:00
Dorian Zedler 14bbd77e71
Fix highlighting bug on registration 2021-06-17 15:36:57 +02:00
Dorian Zedler 2213765a39
Some changes to footer layout 2021-06-17 15:08:34 +02:00
Dorian Zedler c62af2e172
Many improovements, dark mode is now functional 2021-06-17 13:50:18 +02:00
Dorian Zedler 229732762c
added url parser and did some refactoring 2021-06-11 11:49:09 +02:00
Dorian Zedler 6b87344bf1
Some work for dark theme support 2021-06-08 19:23:55 +02:00
Dorian Zedler 1d92099d1d
final changes 2021-06-08 13:38:05 +02:00
Dorian Zedler 1dec380277 Update CHANGELOG.md 2021-06-07 20:13:03 +00:00
Dorian Zedler 100ac5143b Update CHANGELOG.md 2021-06-07 20:12:35 +00:00
Dorian Zedler f6cd1853ae - Some updates to screenshots
- Added mode for large displays
2021-06-07 20:56:53 +02:00
Dorian Zedler 883ee82e29
fix spacing 2021-06-06 22:38:30 +02:00
Dorian Zedler f4aa3deaec
some more minor adjustments 2021-06-06 21:53:41 +02:00
Dorian Zedler f9f6dd7f5d
Many small and big improvements, see change log for details 2021-06-06 18:34:27 +02:00
Dorian Zedler 121589bf29
Some more improvements and styling 2021-06-06 00:15:54 +02:00
Dorian Zedler 0b055bc2c1
- remove IFSC stuff
- new design for startpage
- some rework
2021-06-05 21:18:21 +02:00
198 changed files with 6903 additions and 2183 deletions

30
.drone.yml Normal file
View file

@ -0,0 +1,30 @@
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,2 +1,5 @@
*.pro.user* *.pro.user*
*.DS_Store *.DS_Store
.bundle
vendor
outputs

3
.gitmodules vendored Normal file
View file

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

View file

@ -3,15 +3,75 @@ 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/) 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). 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.03.1] - UR # [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
### Changed ### Changed
- the boulder result rect doesn't have a background if there is no result now - the boulder result rect doesn't have a background if there is no result now
- the selected route is kept when changing cats - 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 # [0.03.0] - 2019-07-11
### Added ### Added
- it is not possible to pin competitions and only show these - it is now possible to pin competitions and only show these
- flowcharts for the speed finals are now available - flowcharts for the speed finals are now available
# [0.02.0] - 2019-06-23 # [0.02.0] - 2019-06-23
@ -21,7 +81,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 - event website url is now present in the cat select dialog if available
### Changed ### Changed
- competitons are clickable if there is a infosheet or event website url available event when they don't have any categories - competitons are clickable if there is a infosheet or event website url available even when they don't have any categories
# [0.01.6] - 2019-06-15 # [0.01.6] - 2019-06-15
### Fixed ### Fixed

3
Gemfile Normal file
View file

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

218
Gemfile.lock Normal file
View file

@ -0,0 +1,218 @@
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

View file

@ -1,3 +1,20 @@
# Digital Rock ranking # Digital Rock ranking
App to view ranking and calendar data from https://www.digitalrock.de/ 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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

View file

@ -1,44 +1,54 @@
<?xml version="1.0"?> <?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"> <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">
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="blueROCK" android:icon="@drawable/icon"> <!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
<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"> Remove the comment if you do not require these default 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. -->
<!-- %%INSERT_FEATURES -->
<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">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<!-- Application arguments --> <!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ --> <!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments --> <!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<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.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.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.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/> <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package --> <!-- 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.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<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 --> <!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="1"/> <meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/> <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_libs_resource_id" android:resource="@array/load_local_libs"/>
<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.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value=""/> <meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Used to specify custom system library path to run with local system libs --> <!-- 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/"/> --> <!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- Messages maps --> <!-- 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_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/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"/> <meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps --> <!-- Messages maps -->
<!-- Splash screen --> <!-- Splash screen -->
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
are done populating your window with content. -->
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ --> <!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ --> <!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen --> <!-- Splash screen -->
<!-- Background running --> <!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the <!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after application still try to draw after
@ -46,38 +56,38 @@
signal is sent! --> signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/> <meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running --> <!-- Background running -->
<!-- auto screen scale factor --> <!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/> <meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor --> <!-- auto screen scale factor -->
<!-- extract android style --> <!-- extract android style -->
<!-- available android:values : <!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps * full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full" * 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 * 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"/> <meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style --> <!-- extract android style -->
</activity> <!-- Handle shared incoming urls -->
<intent-filter android:autoVerify="true">
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices --> <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> </application>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 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.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.CAMERA"/>
<!-- 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> </manifest>

83
android/build.gradle Normal file
View file

@ -0,0 +1,83 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.0'
}
}
repositories {
google()
jcenter()
}
apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
compile 'com.android.support:support-v4:25.3.1'
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qt5AndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
compileSdkVersion androidCompileSdkVersion.toInteger()
buildToolsVersion '28.0.3'
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
tasks.withType(JavaCompile) {
options.incremental = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
}
defaultConfig {
resConfig "en"
minSdkVersion = qtMinSdkVersion
targetSdkVersion = qtTargetSdkVersion
}
lintOptions {
checkReleaseBuilds false
abortOnError false
}
}

11
android/gradle.properties Normal file
View file

@ -0,0 +1,11 @@
# 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

Binary file not shown.

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

172
android/gradlew vendored Executable file
View file

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
android/gradlew.bat vendored Normal file
View file

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View file

@ -0,0 +1,22 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<array name="qt_sources">
<item>https://download.qt.io/ministro/android/qt5/qt-5.14</item>
</array>
<!-- The following is handled automatically by the deployment tool. It should
not be edited manually. -->
<array name="bundled_libs">
<!-- %%INSERT_EXTRA_LIBS%% -->
</array>
<array name="qt_libs">
<!-- %%INSERT_QT_LIBS%% -->
</array>
<array name="load_local_libs">
<!-- %%INSERT_LOCAL_LIBS%% -->
</array>
</resources>

View file

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

View file

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

View file

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

View file

@ -0,0 +1,396 @@
// (c) 2017 Ekkehard Gentz (ekke)
// this project is based on ideas from
// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
// see github project https://github.com/lasconic/ShareUtils-QML
// also inspired by:
// https://www.androidcode.ninja/android-share-intent-example/
// https://www.calligra.org/blogs/sharing-with-qt-on-android/
// https://stackoverflow.com/questions/7156932/open-file-in-another-app
// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
// https://stackoverflow.com/questions/5734678/custom-filtering-of-intent-chooser-based-on-installed-android-package-name
// see also /COPYRIGHT and /LICENSE
package org.ekkescorner.utils;
import org.qtproject.qt5.android.QtNative;
import java.lang.String;
import android.content.Intent;
import java.io.File;
import android.net.Uri;
import android.util.Log;
import android.content.ContentResolver;
import android.database.Cursor;
import android.provider.MediaStore;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.util.List;
import android.content.pm.ResolveInfo;
import java.util.ArrayList;
import android.content.pm.PackageManager;
import java.util.Comparator;
import java.util.Collections;
import android.content.Context;
import android.os.Parcelable;
import android.os.Build;
import android.support.v4.content.FileProvider;
import android.support.v4.app.ShareCompat;
public class QShareUtils
{
// reference Authority as defined in AndroidManifest.xml
private static String AUTHORITY="de.itsblue.blueROCK.fileprovider";
protected QShareUtils()
{
Log.d("ekkescorner", "QShareUtils()");
}
public static boolean checkMimeTypeView(String mimeType) {
if (QtNative.activity() == null)
return false;
Intent myIntent = new Intent();
myIntent.setAction(Intent.ACTION_VIEW);
// without an URI resolve always fails
// an empty URI allows to resolve the Activity
File fileToShare = new File("");
Uri uri = Uri.fromFile(fileToShare);
myIntent.setDataAndType(uri, mimeType);
// Verify that the intent will resolve to an activity
if (myIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
Log.d("ekkescorner checkMime ", "YEP - we can go on and View");
return true;
} else {
Log.d("ekkescorner checkMime", "sorry - no App available to View");
}
return false;
}
public static boolean checkMimeTypeEdit(String mimeType) {
if (QtNative.activity() == null)
return false;
Intent myIntent = new Intent();
myIntent.setAction(Intent.ACTION_EDIT);
// without an URI resolve always fails
// an empty URI allows to resolve the Activity
File fileToShare = new File("");
Uri uri = Uri.fromFile(fileToShare);
myIntent.setDataAndType(uri, mimeType);
// Verify that the intent will resolve to an activity
if (myIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
Log.d("ekkescorner checkMime ", "YEP - we can go on and Edit");
return true;
} else {
Log.d("ekkescorner checkMime", "sorry - no App available to Edit");
}
return false;
}
public static boolean shareText(String text) {
if (QtNative.activity() == null)
return false;
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, text);
sendIntent.setType("text/plain");
// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(QtNative.activity().getPackageManager()) != null) {
QtNative.activity().startActivity(sendIntent);
return true;
} else {
Log.d("ekkescorner share", "Intent not resolved");
}
return false;
}
// thx @oxied and @pooks for the idea: https://stackoverflow.com/a/18835895/135559
// theIntent is already configured with all needed properties and flags
// so we only have to add the packageName of targeted app
public static boolean createCustomChooserAndStartActivity(Intent theIntent, String title, int requestId, Uri uri) {
final Context context = QtNative.activity();
final PackageManager packageManager = context.getPackageManager();
final boolean isLowerOrEqualsKitKat = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT;
// MATCH_DEFAULT_ONLY: Resolution and querying flag. if set, only filters that support the CATEGORY_DEFAULT will be considered for matching.
// Check if there is a default app for this type of content.
ResolveInfo defaultAppInfo = packageManager.resolveActivity(theIntent, PackageManager.MATCH_DEFAULT_ONLY);
if(defaultAppInfo == null) {
Log.d("ekkescorner", title+" PackageManager cannot resolve Activity");
return false;
}
// had to remove this check - there can be more Activity names, per ex
// com.google.android.apps.docs.editors.kix.quickword.QuickWordDocumentOpenerActivityAlias
// if (!defaultAppInfo.activityInfo.name.endsWith("ResolverActivity") && !defaultAppInfo.activityInfo.name.endsWith("EditActivity")) {
// Log.d("ekkescorner", title+" defaultAppInfo not Resolver or EditActivity: "+defaultAppInfo.activityInfo.name);
// return false;
//}
// Retrieve all apps for our intent. Check if there are any apps returned
List<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,17 +1,8 @@
QT += quick qml quickcontrols2 purchasing widgets QT += quick qml quickcontrols2
CONFIG += c++11 CONFIG += c++11
VERSION = 0.04 VERSION = 0.8.0
TARGET = blueROCK
android {
QT += androidextras
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 # The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings # any Qt feature that has been marked deprecated (the exact warnings
@ -24,16 +15,30 @@ DEFINES += QT_DEPRECATED_WARNINGS
# You can also select to disable deprecated APIs only up to a certain version of Qt. # 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 #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 += \
sources/shareUtils/platformshareutils.cpp \
sources/appsettings.cpp \ sources/appsettings.cpp \
sources/main.cpp \ sources/bluerockbackend.cpp \
sources/serverconn.cpp sources/shareUtils/shareutils.cpp \
sources/main.cpp
HEADERS += \
headers/appsettings.h \
headers/bluerockbackend.h \
headers/shareUtils/shareutils.h \
headers/shareUtils/platformshareutils.h
RESOURCES += resources/qml/qml.qrc \ RESOURCES += resources/qml/qml.qrc \
resources/shared/shared.qrc \ resources/shared/shared.qrc \
#resources/shared/icons/bluerock/index.theme \ resources/translations/translations.qrc
#$$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 # Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH = QML_IMPORT_PATH =
@ -46,12 +51,103 @@ qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target !isEmpty(target.path): INSTALLS += target
HEADERS += \
headers/appsettings.h \
headers/serverconn.h
DISTFILES += \ DISTFILES += \
CHANGELOG.md \ CHANGELOG.md \
android-sources/AndroidManifest.xml \ README.md
resources/shared/icons/bluerock/index.theme \
$$files(resources/shared/icons/*.png, true) android {
QT += androidextras
SOURCES += sources/shareUtils/androidshareutils.cpp
HEADERS += headers/shareUtils/androidshareutils.h
OTHER_FILES += android/src/org/ekkescorner/utils/QShareUtils.java \
android/src/org/ekkescorner/utils/QSharePathResolver.java \
android/src/de/itsblue/blueROCK/MainActivity.java \
android/AndroidManifest.xml \
android/build.gradle \
android/gradle.properties \
android/gradle/wrapper/gradle-wrapper.jar \
android/gradle/wrapper/gradle-wrapper.properties \
android/gradlew \
android/gradlew.bat \
android/res/values/libs.xml \
android/res/xml/filepaths.xml
defineReplace(droidVersionCode) {
segments = $$split(1, ".")
for (segment, segments): vCode = "$$first(vCode)$$format_number($$segment, width=3 zeropad)"
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
}
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

2
fastlane/Appfile.android Normal file
View file

@ -0,0 +1,2 @@
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

38
fastlane/Fastfile Normal file
View file

@ -0,0 +1,38 @@
# 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,34 +10,26 @@ class AppSettings : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(NOTIFY themeChanged)
public: public:
explicit AppSettings(QObject *parent = nullptr); explicit AppSettings(QObject *parent = nullptr);
// This is the Constructor of the AppSettings class // This is the Constructor of the AppSettings class
~AppSettings(); ~AppSettings();
// This is the Destructor of the AppSettings class // This is the Destructor of the AppSettings class
private: private:
QSettings *settingsManager; 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: public slots:
Q_INVOKABLE QString read(const QString &key); 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); 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); 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 #endif // APPSETTINGS_H

81
headers/bluerockbackend.h Normal file
View file

@ -0,0 +1,81 @@
/*
blueROCK - for digital rock
Copyright (C) 2019 Dorian Zedler
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <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

View file

@ -0,0 +1,22 @@
#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

@ -1,45 +0,0 @@
/*
blueROCK - for digital rock
Copyright (C) 2019 Dorian Zedler
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SERVERCONN_H
#define SERVERCONN_H
#include <QObject>
#include <QtNetwork>
#include <QEventLoop>
#include <QTimer>
#include <QUrl>
class ServerConn : public QObject
{
Q_OBJECT
public:
explicit ServerConn(QObject *parent = nullptr);
private:
QVariantMap senddata(QUrl serviceUrl, QUrlQuery pdata = QUrlQuery());
signals:
public slots:
QVariant getWidgetData(QVariantMap params);
};
#endif // SERVERCONN_H

View file

@ -0,0 +1,51 @@
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
// my blog about Qt for mobile: http://j.mp/qt-x
// see also /COPYRIGHT and /LICENSE
#ifndef ANDROIDSHAREUTILS_H
#define ANDROIDSHAREUTILS_H
#include <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

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

View file

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

View file

@ -0,0 +1,51 @@
// (c) 2017 Ekkehard Gentz (ekke)
// this project is based on ideas from
// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
// see github project https://github.com/lasconic/ShareUtils-QML
// also inspired by:
// https://www.androidcode.ninja/android-share-intent-example/
// https://www.calligra.org/blogs/sharing-with-qt-on-android/
// https://stackoverflow.com/questions/7156932/open-file-in-another-app
// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
// see also /COPYRIGHT and /LICENSE
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
// my blog about Qt for mobile: http://j.mp/qt-x
// see also /COPYRIGHT and /LICENSE
#ifndef PLATFORMSHAREUTILS_H
#define PLATFORMSHAREUTILS_H
#include <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

63
headers/shareUtils/shareutils.h Executable file
View file

@ -0,0 +1,63 @@
// (c) 2017 Ekkehard Gentz (ekke)
// this project is based on ideas from
// http://blog.lasconic.com/share-on-ios-and-android-using-qml/
// see github project https://github.com/lasconic/ShareUtils-QML
// also inspired by:
// https://www.androidcode.ninja/android-share-intent-example/
// https://www.calligra.org/blogs/sharing-with-qt-on-android/
// https://stackoverflow.com/questions/7156932/open-file-in-another-app
// http://www.qtcentre.org/threads/58668-How-to-use-QAndroidJniObject-for-intent-setData
// see also /COPYRIGHT and /LICENSE
// (c) 2017 Ekkehard Gentz (ekke) @ekkescorner
// my blog about Qt for mobile: http://j.mp/qt-x
// see also /COPYRIGHT and /LICENSE
#ifndef SHAREUTILS_H
#define SHAREUTILS_H
#include <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

45
ios/Info.plist Normal file
View file

@ -0,0 +1,45 @@
<?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>

10
ios/blueROCK.entitlements Normal file
View file

@ -0,0 +1,10 @@
<?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 Submodule

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

View file

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

View file

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

View file

@ -0,0 +1,32 @@
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

@ -0,0 +1,262 @@
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

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

View file

@ -0,0 +1,166 @@
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

@ -64,21 +64,6 @@ ListView {
} }
} }
InfoArea {
id: infoArea
anchors {
left: control.left
right: control.right
top: control.top
margins: app.landscape() ? control.width * 0.4:control.width * 0.3
topMargin: control.height*( status === 901 ? 0.6:0.5) - height * 0.8
}
excludedCodes: [200, 902, 905]
errorCode: control.status
}
PullRefresher { PullRefresher {
target: control target: control

View file

@ -0,0 +1,59 @@
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,47 +37,25 @@ BusyIndicator {
anchors.fill: parent anchors.fill: parent
property int currentHeight: 0 RotationAnimation {
target: blueRockHoldIcon
SequentialAnimation {
running: control.running running: control.running
duration: control.animationSpeed
loops: Animation.Infinite loops: Animation.Infinite
from: 0
NumberAnimation { to: 360
target: item easing.type: Easing.InOutQuad
property: "currentHeight"
from: 0
to: 800
duration: control.animationSpeed
}
} }
Row {
Image {
id: blueRockHoldIcon
anchors.fill: parent anchors.fill: parent
spacing: item.width / 9 source: "qrc:/icons/blueRockHold.png"
mipmap: true
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,18 +19,20 @@
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick.Controls.Material 2.12
import QtQuick.Controls.Material.impl 2.12
ToolButton { MouseArea {
id: control id: control
property string image property string image
property color backgroundColor: "white" property color backgroundColor: "white"
property color textColor: "black" property color textColor: "black"
property real imageScale: 1 property real imageScale: 1.3
property double glowRadius: 0.001 property double glowRadius: 0.001
property double glowSpread: 0.2 property double glowSpread: 0.2
property bool glowVisible: true property bool glowVisible: true
property double glowScale: 0.75 property double glowScale: 0.92
property double glowOpacity: 1 property double glowOpacity: 1
Behavior on backgroundColor { Behavior on backgroundColor {
@ -39,7 +41,7 @@ ToolButton {
} }
} }
contentItem: Item { Item {
id: controlBackgroundContainer id: controlBackgroundContainer
anchors.fill: parent anchors.fill: parent
@ -65,9 +67,9 @@ ToolButton {
anchors.fill: parent anchors.fill: parent
radius: height * 0.5 radius: height * 0.2
color: control.down ? Qt.darker(control.backgroundColor, 1.03) : control.backgroundColor color: Material.dialogColor //control.down ? Qt.darker(control.backgroundColor, 1.03) : control.backgroundColor
Image { Image {
id: buttonIcon id: buttonIcon
@ -84,22 +86,30 @@ ToolButton {
scale: control.imageScale scale: control.imageScale
} }
Behavior on color { Ripple {
ColorAnimation { id: ripple
duration: 100 visible: true
clipRadius: controlBackground.radius
clip: true
width: parent.width
height: parent.height
pressed: control.pressed
anchor: control
active: control.pressed || control.visualFocus || control.containsMouse
color: control.Material.rippleColor
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Item {
width: ripple.width
height: ripple.height
Rectangle {
anchors.fill: parent
radius: controlBackground.radius
}
}
} }
} }
} }
} }
Text {
id: conetntText
text: qsTr(control.text)
anchors.centerIn: parent
font: parent.font
color: control.textColor
opacity: control.enabled ? 1:0.4
}
} }

View file

@ -0,0 +1,97 @@
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

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

View file

@ -18,6 +18,7 @@
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.12
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
Item { Item {
@ -32,14 +33,10 @@ Item {
property int postRefreshDelay: 1000 // delay after reload funcion has finished property int postRefreshDelay: 1000 // delay after reload funcion has finished
property int preRefreshDelay: 1000 // delay before reload funcion is called property int preRefreshDelay: 1000 // delay before reload funcion is called
property int refreshPosition: height * 1.2 // position of the item when refreshing property int refreshPosition: height * 1.7 // position of the item when refreshing
property int dragOutPosition: height * 1.8 // maximum drag out property int dragOutPosition: height * 1.8 // maximum drag out
property double dragRefreshPositionMultiplier: 0.5 // position of the item when starting to refresh property double dragRefreshPositionMultiplier: 0.6 // position of the item when starting to refresh
property color backgroundColor: "white" // color for the pre-defined background
property color pullIndicatorColor: "black" // color for the pre-defined pull indicator
//property color busyIndicatorColor: "pink" // color for the pre-defined busy indicator
readonly property double dragProgress: Math.min( userPosition / dragOutPosition, 1) readonly property double dragProgress: Math.min( userPosition / dragOutPosition, 1)
@ -61,7 +58,7 @@ Item {
anchors.fill: parent anchors.fill: parent
radius: width * 0.5 radius: width * 0.5
color: control.backgroundColor color: Material.dialogColor
} }
} }
property Component busyIndicator: BusyIndicator { running: true } property Component busyIndicator: BusyIndicator { running: true }
@ -107,7 +104,7 @@ Item {
ctx.reset() ctx.reset()
ctx.lineWidth = lineWidth; ctx.lineWidth = lineWidth;
ctx.strokeStyle = control.pullIndicatorColor; ctx.strokeStyle = control.Material.foreground;
// middle line // middle line
ctx.moveTo(width/2, topMargin); ctx.moveTo(width/2, topMargin);
@ -162,7 +159,7 @@ Item {
Connections { Connections {
target: control.target target: control.target
onDragEnded: { function onDragEnded() {
if(userPosition >= control.dragOutPosition * control.dragRefreshPositionMultiplier){ if(userPosition >= control.dragOutPosition * control.dragRefreshPositionMultiplier){
control.state = "refreshing" control.state = "refreshing"
preRefreshTimer.start() preRefreshTimer.start()

View file

@ -0,0 +1,158 @@
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

@ -0,0 +1,136 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.3
Dialog {
id: control
property var dataObj
property string subTitle: ""
signal selectionFinished(int index, var data)
signal linkActivated(string link)
parent: Overlay.overlay
x: 0
y: parent.height - implicitHeight
opacity: 1
width: parent.width
implicitWidth: width
contentHeight: Math.min(parent.height * 0.7, implicitContentHeight)
implicitHeight: contentHeight + topPadding + bottomPadding + header.height
padding: 30
modal: true
focus: true
title: ""
header: Column {
id: selectorPuHeaderCol
width: control.width
height: headerLa.height + headerTopSpacerItm.height + (control.subTitle ? headerSubLa.height:0)
Item {
id: headerTopSpacerItm
height: control.padding
width: parent.width
}
MovingLabel {
id: headerLa
anchors.horizontalCenter: parent.horizontalCenter
width: selectorPuHeaderCol.width - control.padding * 2
font.bold: true
font.pixelSize: 16
text: control.title
onLinkActivated: control.linkActivated(link)
}
Label {
id: headerSubLa
anchors.horizontalCenter: parent.horizontalCenter
width: selectorPuHeaderCol.width - control.padding * 2
wrapMode: Text.Wrap
topPadding: 5
bottomPadding: 0
font.bold: true
font.pixelSize: 16
text: control.subTitle
onLinkActivated: {
control.linkActivated(link)
}
}
}
background: Item {
Rectangle {
id: backgroundRect
anchors {
fill: parent
bottomMargin: -radius
}
radius: control.leftPadding
color: control.Material.dialogColor
}
}
function appear(dataObj, title, subTitle) {
if(dataObj.length > 0){
control.dataObj = dataObj
}
else {
control.dataObj = undefined
}
control.title = title
control.subTitle = subTitle === undefined ? "":subTitle
control.open()
}
enter: Transition {
NumberAnimation {
property: "opacity";
from: 0
to: 1.0
easing.type: Easing.Linear
}
NumberAnimation {
property: "y"
from: control.parent.height - control.implicitHeight * 0.7
to: control.parent.height - control.implicitHeight
}
}
exit: Transition {
NumberAnimation {
property: "opacity";
from: 1
to: 0
}
NumberAnimation {
property: "y"
from: control.parent.height - control.implicitHeight
to: control.parent.height - control.implicitHeight * 0.7
}
}
}

View file

@ -0,0 +1,124 @@
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

@ -0,0 +1,289 @@
.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,392 +20,326 @@ import QtQuick 2.10
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.1 import QtQuick.Controls.Material 2.1
import QtGraphicalEffects 1.0
ListView { import "SpeedFlowChart.js" as SpeedFlowChart
Item {
id: control id: control
property var flowchartData property var flowchartData
property var allFlowchartData property var allFlowchartData
property int rounds: 0 property int rounds: 0
property int tileSize: app.height / 8 * 0.8
property int refreshes: 0 property int refreshes: 0
property int roundRefreshes: 1 property int roundRefreshes: 1
anchors.fill: parent
anchors.margins: 10
spacing: app.width * 0.1
orientation: ListView.LeftToRight
boundsBehavior: ListView.StopAtBounds
onFlowchartDataChanged: { onFlowchartDataChanged: {
prepareData() prepareData()
} }
model: 0
function prepareData() { function prepareData() {
if(!control.enabled || control.flowchartData === undefined || control.flowchartData['route_names'] === undefined) if(!control.enabled || control.flowchartData === undefined || control.flowchartData['route_names'] === undefined)
return return
/*refreshes += 1
if(refreshes > 2){ var flowchartResult = SpeedFlowChart.convertResultsToSpeedFlowchartResult(control.flowchartData)
roundRefreshes += 1 const l = flowchartResult.rounds.length;
} const dummy = { dummy: true };
flowchartResult.rounds[l - 2].pairs = [
console.log("refreshes: " + refreshes + " rounds: " + roundRefreshes) dummy,
...flowchartResult.rounds[l - 1].pairs,
// create competition like data (just testing) ...flowchartResult.rounds[l - 2].pairs,
for(var part in flowchartData['participants']){ ];
if(flowchartData['participants'].hasOwnProperty(part)){ flowchartResult.rounds[l - 2].roundName = `${
flowchartResult.rounds[l - 1].roundName
for(var r = 2 + roundRefreshes; r < 7; r++){ } / ${flowchartResult.rounds[l - 2].roundName}`;
delete flowchartData['participants'][part]["result"+r] flowchartResult.rounds.pop();
delete flowchartData['participants'][part]["result_rank"+r] control.allFlowchartData = flowchartResult
}
if(parseInt(flowchartData['participants'][part]["result_rank0"]) > 14 + refreshes) {
delete flowchartData['participants'][part]["result_rank2"]
}
}
}
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]
*/
//flowchartData['route_names'] = flowchartData['route_names'].slice(0,)
// 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
}
if(thisWinnerIsFirstOfNewPair){
nextRound.push([])
}
//thisPair[0]["result_rank"] = thisPair[0]["result_rank"+round]
//thisPair[1]["result_rank"] = thisPair[1]["result_rank"+round]
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
}
//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)
}
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 { ListView {
id: roundCol id: roundListView
property int thisIndex: index property int columnWidth: height * 0.4
property int thisRound: thisRoundIsValid ? control.allFlowchartData[roundCol.thisIndex][control.allFlowchartData[roundCol.thisIndex].length-2]:-1 property int columnHeight: height
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 anchors {
height: control.height top: parent.top
bottom: parent.bottom
//spacing: app.width * 0.1 horizontalCenter: parent.horizontalCenter
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 { width: Math.min((columnWidth + spacing) * model, control.width)
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) spacing: app.height * 0.05
property var matchData: roundCol.thisRoundIsValid ? control.allFlowchartData[ thisIsSmallFinal ? roundCol.thisIndex+1 : roundCol.thisIndex][ thisIsSmallFinal ? 0:matchItm.thisIndex]:undefined orientation: ListView.LeftToRight
boundsBehavior: ListView.StopAtBounds
property int thisIndex: index model: control.allFlowchartData.rounds.length
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) delegate: Item {
property bool thisMatchIsOver: thisMatchDataIsValid && thisMatchData[0]['result_rank'+thisRound] !== undefined && thisMatchData[1]['result_rank'+thisRound] !== undefined id: roundItem
property bool thisIsFinal: roundCol.thisIsLastRound && thisIndex === rectRep.model - 2 property int thisIndex: index
property bool thisIsSmallFinal: roundCol.thisIsLastRound && thisIndex === rectRep.model - 1 property var thisData: control.allFlowchartData.rounds[thisIndex]
property int winnerIndex: thisMatchIsOver ? (parseInt(thisMatchData[0]['result_rank'+thisRound]) < parseInt(thisMatchData[1]['result_rank'+thisRound]) ? 0:1) : -1 property bool thisRoundIsValid: true
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 ) property bool thisIsLastRound: thisIndex === roundListView.model - 1
width: parent.width property bool thisIsSemiFinal: thisIndex === roundListView.model - 2
onMatchDataChanged: { property int tileSize: (roundItem.height / 8 - roundNameLa.height) * 1.45
fadeInPa.start()
}
ParallelAnimation { width: roundListView.columnWidth
id: fadeInPa height: roundListView.columnHeight
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 { Column {
id: lineCanvas id: roundCol
width: app.width; height: app.height
contextType: "2d"
visible: !roundCol.thisIsLastRound anchors.fill: parent
property int targetX: matchItm.width + control.spacing Label {
property int targetY: matchItm.lowerPart ? 0:matchItm.height id: roundNameLa
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
anchors {
centerIn: !matchItm.thisIsFinal ? parent:undefined
bottom: matchItm.thisIsFinal ? parent.bottom:undefined
}
//anchors.verticalCenterOffset: matchItm.lowerPart ? 10:10
width: parent.width width: parent.width
height: control.tileSize height: control.height * 0.05
//scale: 0.9 horizontalAlignment: Text.AlignHCenter
color: "white" verticalAlignment: Text.AlignVCenter
border.color: "lightgrey" fontSizeMode: Text.Fit
border.width: 1 font.pixelSize: height * 0.6
radius: height * 0.2 minimumPixelSize: 1
Material.elevation: 10 font.bold: true
text: roundItem.thisData.roundName ?
roundItem.thisData.roundName :
"-"
}
Grid { Repeater {
columns: app.landscape() ? 2:0 id: rectRep
rows: app.landscape() ? 0:2 model: roundItem.thisData.pairs.length
spacing: app.landscape() ? height * 0.4:0 delegate: Item {
id: matchItm
anchors.fill: parent property bool lowerPart: (index%2 > 0)
anchors.margins: matchRect.radius * 0.5 property var thisData: roundItem.thisData.pairs[index]
property int thisIndex: index
Repeater { height: (roundItem.height - roundNameLa.height) / rectRep.model - roundCol.spacing
// for the two athletes width: roundItem.width
model: 2
delegate: RowLayout { onThisDataChanged: {
fadeInPa.start()
}
height: app.landscape() ? parent.height:parent.height / 2 - parent.spacing ParallelAnimation {
width: app.landscape() ? parent.width / 2 - parent.spacing : parent.width 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 }
}
spacing: app.landscape() ? height * 0.1:height * 0.1 Canvas {
id: lineCanvas
width: app.width; height: app.height
contextType: "2d"
Label { visible: !roundItem.thisIsLastRound
Layout.preferredHeight: parent.height
height: parent.height property int targetYFactor: matchItm.lowerPart ? -1:1
verticalAlignment: Text.AlignVCenter Path {
font.pixelSize: height * 0.7 id: firstPartPath
fontSizeMode: Text.Fit startX: matchItm.width
minimumPixelSize: 1 startY: matchItm.height * 0.5
Layout.alignment: Layout.Left PathQuad {
id: firstPartPathQuad
text: matchItm.thisMatchData[index] !== undefined ? (parseInt(matchItm.thisMatchData[index]['result_rank0']) < 10 ?matchItm.thisMatchData[index]['result_rank0'] + " ":matchItm.thisMatchData[index]['result_rank0']): "" relativeX: roundListView.spacing * 0.5
relativeY: matchItm.height * 0.25 * lineCanvas.targetYFactor
relativeControlX: relativeX
relativeControlY: 0
} }
Label { PathQuad {
Layout.preferredHeight: parent.height relativeX: roundListView.spacing * 0.5
relativeY: matchItm.height * 0.25 * lineCanvas.targetYFactor
height: parent.height relativeControlX: 0
relativeControlY: relativeY
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 { onPaint: {
Layout.preferredHeight: parent.height context.strokeStyle = "lightgrey"
context.lineWidth = matchRect.height * 0.05
context.path = firstPartPath;
context.stroke();
}
}
height: parent.height RectangularGlow {
id: effect
visible: matchRect.visible
anchors.fill: matchRect
glowRadius: 0
spread: 0
opacity: 0.3
color: "black"
cornerRadius: matchRect.radius
}
verticalAlignment: Text.AlignVCenter Rectangle {
font.pixelSize: app.landscape() ? height * 0.35 : height * 0.5 id: matchRect
Layout.alignment: Layout.Right anchors {
centerIn: !matchItm.thisIsFinal ? parent:undefined
bottom: matchItm.thisIsFinal ? parent.bottom:undefined
}
text: matchItm.thisMatchData[index] !== undefined && matchItm.thisMatchData[index]['result'+matchItm.thisRound] !== undefined ? visible: !matchItm.thisData.dummy
( parseFloat(matchItm.thisMatchData[index]['result'+matchItm.thisRound]) ?
(parseFloat(matchItm.thisMatchData[index]['result'+matchItm.thisRound]).toFixed(2)) width: parent.width
: matchItm.thisMatchData[index]['result'+matchItm.thisRound] ) 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 : ""
}
}
} }
} }
} }
} }
} }
}
Loader {
id: blueRockBadgeLoader
x: (parent.width - width) / 2
y: (parent.height - roundNameLa.height - height) / 2 + roundNameLa.height
sourceComponent: roundItem.thisIsSemiFinal ? blueRockBadgeComponent:undefined
Component {
id: blueRockBadgeComponent
BlueRockBadge {
width: roundItem.width * 0.8
height: width * 0.25
}
}
} }
} }
} }

View file

@ -0,0 +1,131 @@
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

@ -0,0 +1,124 @@
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

@ -0,0 +1,79 @@
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,6 +14,7 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: 1 anchors.margins: 1
anchors.bottomMargin: indicator.height anchors.bottomMargin: indicator.height
spacing: width * 0.2
Repeater { Repeater {
model: control.images.length model: control.images.length

View file

@ -0,0 +1,298 @@
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

@ -16,8 +16,10 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.0 import QtQuick 2.9
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.0
import QtQuick.Controls.Material 2.12
import "../Components" import "../Components"
@ -25,54 +27,41 @@ Page {
id: root id: root
title: "start" title: "start"
property Component headerComponent: null
Label { signal headerComponentChanged()
BlueRockBadge {
id: headerBadge
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
top: parent.top top: parent.top
topMargin: root.height * 0.03 topMargin: root.height * 0.03
} }
font.pixelSize: anchors.topMargin height: app.landscape() ? menuGr.buttonSize * 0.2:menuGr.buttonSize * 0.3
font.bold: true
text: "blueROCK"
} }
Grid { GridLayout {
id: menuGr id: menuGr
anchors.centerIn: parent anchors.centerIn: parent
rows: app.landscape() ? 1:3 rows: app.landscape() ? 1:2
columns: app.landscape() ? 3:1 columns: app.landscape() ? 2:1
spacing: !app.landscape() ? parent.height * 0.08:parent.width * 0.1 rowSpacing: app.landscape() ? parent.width * 0.1:headerBadge.anchors.topMargin
columnSpacing: rowSpacing
property int buttonSize: app.landscape() ? parent.width * 0.2:parent.height * 0.2 property int buttonSize: app.landscape() ? parent.width * 0.2:parent.height * 0.19
FancyButton {
id: ifscBt
height: menuGr.buttonSize
width: height
image: "qrc:/icons/ifsc.png"
onClicked: {
app.openWidget({nation:"ICC"})
}
}
FancyButton { FancyButton {
id: davBt id: davBt
height: menuGr.buttonSize Layout.preferredHeight: menuGr.buttonSize
width: height Layout.preferredWidth: menuGr.buttonSize
Layout.alignment: Layout.Center
image: "qrc:/icons/dav.png" image: Material.theme === Material.Dark ? "qrc:/icons/dav-dark.png":"qrc:/icons/dav.png"
onClicked: { onClicked: {
app.openWidget({nation:"GER"}) app.openWidget({nation:"GER"})
@ -83,37 +72,107 @@ Page {
FancyButton { FancyButton {
id: sacBt id: sacBt
height: menuGr.buttonSize Layout.preferredHeight: menuGr.buttonSize
width: height Layout.preferredWidth: menuGr.buttonSize
Layout.alignment: Layout.Center
image: "qrc:/icons/sac.png" image: Material.theme === Material.Dark ? "qrc:/icons/sac-dark.png":"qrc:/icons/sac.png"
onClicked: { onClicked: {
app.openWidget({nation:"SUI"}) app.openWidget({nation:"SUI"})
} }
} }
} }
Label { Grid {
id: footerMenu
anchors { anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom bottom: parent.bottom
bottomMargin: root.height * 0.03 margins: headerBadge.anchors.topMargin
horizontalCenter: parent.horizontalCenter
} }
width: parent.width * 0.9 width: app.landscape() ? childrenRect.width : parent.width * 0.8
height: anchors.bottomMargin height: app.landscape() ? headerBadge.height : Math.min(headerBadge.height * 2, width * 0.27)
wrapMode: "Wrap" columnSpacing: height * 0.1
horizontalAlignment: Text.AlignHCenter
text: "Resultservice and rankings provided by <a href='http://www.digitalROCK.de'>digital ROCK</a>." columns: app.landscape() ? 4:2
rows: app.landscape() ? 1:2
onLinkActivated: { Repeater {
Qt.openUrlExternally(link) 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]()
}
}
} }
} }
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,7 +38,9 @@ Page {
Result, Result,
Ranking, Ranking,
Aggregated // not yet implemented Aggregated, // not yet implemented
Invalid
} }
title: widgetLd.item !== null && widgetLd.item.hasOwnProperty('title') ? widgetLd.item['title']:"" title: widgetLd.item !== null && widgetLd.item.hasOwnProperty('title') ? widgetLd.item['title']:""
@ -46,6 +48,10 @@ Page {
property bool titleIsPageTitle: widgetLd.item !== null && widgetLd.item.hasOwnProperty('titleIsPageTitle') ? widgetLd.item['titleIsPageTitle']:false 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 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 var params
property int status: -1 property int status: -1
@ -68,7 +74,6 @@ Page {
// route: (int) round // route: (int) round
// type: ('','starters', 'nat_team_ranking', 'sektionenwertung', 'regionalzentren'), // type: ('','starters', 'nat_team_ranking', 'sektionenwertung', 'regionalzentren'),
//} //}
var ret = serverConn.getWidgetData(params) var ret = serverConn.getWidgetData(params)
root.status = ret["status"] root.status = ret["status"]
@ -76,7 +81,11 @@ Page {
if(ret["status"] === 200){ if(ret["status"] === 200){
root.widgetData = ret["data"] root.widgetData = ret["data"]
root.widgetType = checkWidgetType(params, root.widgetData) root.widgetType = checkWidgetType(params, root.widgetData)
if(widgetLd.load()){ if(widgetType === WidgetPage.WidgetType.Invalid) {
root.ready = false
root.status = 906
}
else if(widgetLd.load()){
root.ready = true root.ready = true
} }
else { else {
@ -84,7 +93,13 @@ Page {
root.ready = false 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 // if we get a 404 and have startlist, results or registration, the route was not found -> remove round and try again
root.params["route"] = "" root.params["route"] = ""
loadData(root.params) loadData(root.params)
@ -114,6 +129,10 @@ Page {
} }
function areParamsValid() {
}
function checkWidgetType(params, widgetData){ function checkWidgetType(params, widgetData){
var widgetType var widgetType
@ -160,10 +179,25 @@ Page {
// aggregated // aggregated
widgetType = WidgetPage.WidgetType.Aggregated widgetType = WidgetPage.WidgetType.Aggregated
} }
else {
widgetType = WidgetPage.WidgetType.Invalid
}
return widgetType 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 { Loader {
id: widgetLd id: widgetLd
@ -197,9 +231,6 @@ Page {
delete(widgetLd.sourceComponent) delete(widgetLd.sourceComponent)
return false return false
} }
//
} }
function getFile(widgetType) { function getFile(widgetType) {
@ -241,107 +272,19 @@ Page {
} }
Dialog { SelectorPopup {
id: selectorPu id: selectorPu
property var dataObj Material.theme: root.Material.theme
property string subTitle: ""
signal selectionFinished(int index, var data) contentItem: ListView {
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 id: selectorLv
property int delegateHeight: 50 property int delegateHeight: 50
spacing: 10
clip: true
anchors.fill: parent implicitHeight: model === 0 ? 0:(delegateHeight + spacing) * model
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 model: selectorPu.dataObj !== undefined ? selectorPu.dataObj.length:0
@ -354,13 +297,12 @@ Page {
leftMargin: 3 leftMargin: 3
bottom: selectorLv.bottom bottom: selectorLv.bottom
} }
} }
delegate: Button { delegate: Button {
id: catBt id: catBt
width: parent.width width: selectorLv.width
height: text !== "" ? selectorLv.delegateHeight:0 height: text !== "" ? selectorLv.delegateHeight:0
flat: true flat: true
@ -373,32 +315,11 @@ 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,6 +19,7 @@
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.12
import "../Components" import "../Components"
@ -27,12 +28,12 @@ DataListView {
property bool ready property bool ready
property string title: (params.nation === "ICC" ? "IFSC":params.nation === "GER" ? "DAV":"SAC") + " " + qsTr("calendar") + " " + control.year //% "calendar"
property string title: (params.nation === "ICC" ? "IFSC":params.nation === "GER" ? "DAV":"SAC") + " " + qsTrId("#calendar") + " " + control.year
property Component headerComponent: RowLayout { property Component headerComponent: RowLayout {
height: parent.height height: parent.height
//width: 10//childrenRect.width
spacing: 0 spacing: 0
ToolButton { ToolButton {
@ -42,7 +43,9 @@ DataListView {
control.changeYear() control.changeYear()
} }
icon.name: "year" text: "\uf133"
font.family: fa5solid.name
} }
ToolButton { ToolButton {
@ -61,16 +64,19 @@ DataListView {
} }
} }
compCats.push( {"text": qsTr("Pinned"), "data": {"sort_rank":0, "cat_id":[-1]}} ) //% "Favorites"
compCats.push( {"text": qsTrId("#favorites"), "data": {"sort_rank":0, "cat_id":[-1]}} )
compCats.sort(function(a, b) { compCats.sort(function(a, b) {
return a['data']['sort_rank'] - b['data']['sort_rank']; return a['data']['sort_rank'] - b['data']['sort_rank'];
}); });
filterSelectPu.appear(compCats, qsTr("Select Filters"), "") //% "Select filters"
filterSelectPu.appear(compCats, qsTrId("#selectFilters"), "")
} }
icon.name: "filter" text: "\uf0b0"
font.family: fa5solid.name
} }
ToolButton { ToolButton {
@ -80,7 +86,8 @@ DataListView {
control.openCup() control.openCup()
} }
icon.name: "cup" text: "\uf091"
font.family: fa5solid.name
} }
} }
@ -106,10 +113,15 @@ DataListView {
initFilters() initFilters()
initFavorites() initFavorites()
if(model){ if(model && widgetData["competitions"].length > 0){
control.status = 200 control.status = 200
control.ready = true control.ready = true
} }
else if(widgetData["competitions"].length === 0) {
control.status = 404
control.ready = false
}
else { else {
control.ready = false control.ready = false
control.status = 901 control.status = 901
@ -151,7 +163,7 @@ DataListView {
if(endDate.getTime() < new Date().getTime()){ if(endDate.getTime() < new Date().getTime()){
// end date is already over -> move the list view down! // end date is already over -> move the list view down!
control.positionViewAtIndex(i, ListView.Top) control.positionViewAtIndex(i - 2, ListView.Top)
//console.log("moving down!") //console.log("moving down!")
} }
} }
@ -193,13 +205,18 @@ DataListView {
var infoUrls = getCompInfoUrls(compIndex) var infoUrls = getCompInfoUrls(compIndex)
var infosheet = ""; var infosheet = "";
if(infoUrls.length >= 1) if(infoUrls.length >= 1)
infosheet += ("<a href='" + getCompInfoUrls(compIndex)[0] + "'>" + qsTr('infosheet') + "</a>") //% "Infosheet"
infosheet += ("<a href='" + getCompInfoUrls(compIndex)[0] + "'>" + qsTrId("#infosheet") + "</a>")
if(infoUrls.length === 2) if(infoUrls.length === 2)
infosheet += (", <a href='" + getCompInfoUrls(compIndex)[1] + "'>" + qsTr('further infos') + "</a>") //% "Further infos"
infosheet += (", <a href='" + getCompInfoUrls(compIndex)[1] + "'>" + qsTrId("#furtherInfos") + "</a>")
console.log("Infosheet: " + infosheet) console.log("Infosheet: " + infosheet)
var eventWebsite = control.widgetData["competitions"][compIndex]["homepage"] !== undefined ? ("<a href='" + control.widgetData["competitions"][compIndex]["homepage"] + "'>" + qsTr('Event Website') + "</a>"):""
var eventWebsite = control.widgetData["competitions"][compIndex]["homepage"] !== undefined ?
//% "Event website"
("<a href='" + control.widgetData["competitions"][compIndex]["homepage"] + "'>" + qsTrId("#eventWebsite") + "</a>"):""
selector.appear(selectOptions, control.widgetData["competitions"][compIndex]['name'], eventWebsite + ((eventWebsite !== "" && infosheet !== "") ? ", ":"") + infosheet ) selector.appear(selectOptions, control.widgetData["competitions"][compIndex]['name'], eventWebsite + ((eventWebsite !== "" && infosheet !== "") ? ", ":"") + infosheet )
} }
@ -215,7 +232,8 @@ DataListView {
} }
} }
selector.appear(selectOptions, qsTr("select year")) //% "Select year"
selector.appear(selectOptions, qsTrId("#selectYear"))
} }
function openCup(state, data) { function openCup(state, data) {
@ -227,7 +245,8 @@ DataListView {
if(state === undefined){ if(state === undefined){
// opened for the first time -> select cup // opened for the first time -> select cup
selectTitle = qsTr("select cup") //% "Select cup"
selectTitle = qsTrId("#selectCup")
cups.sort(function(a, b) { cups.sort(function(a, b) {
return parseInt(b["SerId"]) - parseInt(a["SerId"]); return parseInt(b["SerId"]) - parseInt(a["SerId"]);
@ -255,7 +274,8 @@ DataListView {
return return
} }
selectTitle = cup['name'] + ": " + qsTr("select category") //% "Select category"
selectTitle = cup['name'] + ": " + qsTrId("#selectCategory")
// build a list with all cat in the cup out of the cat keys (rkey) given in the cup.cats // 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']){ for(prop in cup['cats']){
@ -369,9 +389,8 @@ DataListView {
Connections { Connections {
target: parent.selector target: parent.selector
onSelectionFinished: { function onSelectionFinished(index, data) {
if(data.comp !== undefined){ if(data.comp !== undefined){
//console.log(data.status)
app.openWidget({comp: data.comp, cat: data.cat, type:data.status === 4 ? 'starters':''}) app.openWidget({comp: data.comp, cat: data.cat, type:data.status === 4 ? 'starters':''})
} }
else if(data.year !== undefined){ else if(data.year !== undefined){
@ -385,6 +404,10 @@ DataListView {
app.openWidget({cup: data.cup, cat: data.cat}) app.openWidget({cup: data.cup, cat: data.cat})
} }
} }
function onLinkActivated(link) {
Qt.openUrlExternally(link)
}
} }
header: Item { header: Item {
@ -399,281 +422,22 @@ DataListView {
height: 10 height: 10
} }
delegate: ItemDelegate { delegate: CompetitionCalendarDelegate {
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"
}
} }
section.property: "month" SelectorPopup {
section.delegate: ItemDelegate {
id: name
background: Rectangle {
color: "red"
}
width: parent.width
text: section
}
Dialog {
id: filterSelectPu id: filterSelectPu
property var dataObj Material.theme: control.Material.theme
property string subTitle: ""
signal selectionFinished(int index, var data) contentItem: ListView {
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 id: selectorLv
property int delegateHeight: 50 property int delegateHeight: 50
spacing: 10
clip: true
anchors.fill: parent implicitHeight: model === 0 ? 0:(delegateHeight + spacing) * model
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 model: filterSelectPu.dataObj !== undefined ? filterSelectPu.dataObj.length:0
@ -686,13 +450,12 @@ DataListView {
leftMargin: 3 leftMargin: 3
bottom: selectorLv.bottom bottom: selectorLv.bottom
} }
} }
delegate: CheckDelegate { delegate: CheckDelegate {
id: catBt id: catBt
width: parent.width width: selectorLv.width
height: text !== "" ? selectorLv.delegateHeight:0 height: text !== "" ? selectorLv.delegateHeight:0
//flat: true //flat: true
@ -705,7 +468,7 @@ DataListView {
Connections { Connections {
target: control target: control
onDisplayedCompCatsChanged: { function onDisplayedCompCatsChanged() {
//console.log("filters changed") //console.log("filters changed")
//competitionDel.visible = control.displayedCompCats.indexOf(parseInt(competitionDel.thisData['cat_id'])) >= 0 //competitionDel.visible = control.displayedCompCats.indexOf(parseInt(competitionDel.thisData['cat_id'])) >= 0
checked = getCheckedState() checked = getCheckedState()
@ -733,31 +496,5 @@ 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,6 +19,7 @@
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.3 import QtQuick.Controls.Material 2.3
import QtGraphicalEffects 1.0
import "../Components" import "../Components"
@ -34,6 +35,15 @@ Page {
property var widgetData: currentWidgetData property var widgetData: currentWidgetData
property Component headerComponent: ToolButton {
id: shareToolBt
onClicked: shareWidget(control.title)
text: "\uf1e0"
font.family: fa5solid.name
}
Component.onCompleted: { Component.onCompleted: {
control.ready = true control.ready = true
control.status = 200 control.status = 200
@ -43,25 +53,11 @@ Page {
id: mainSv id: mainSv
anchors.fill: parent anchors.fill: parent
anchors.margins: 10 anchors.margins: 20
anchors.rightMargin: 14
contentWidth: parent.width - anchors.leftMargin - anchors.rightMargin contentWidth: parent.width - anchors.margins * 2
ScrollBar.vertical: ScrollBar { ScrollBar.vertical.policy: ScrollBar.AlwaysOff
anchors {
top: mainSv.top
left: mainSv.right
margins: 10
leftMargin: 3
bottom: mainSv.bottom
}
width: 8
active: true
}
Column { Column {
id: mainCol id: mainCol
@ -72,25 +68,61 @@ Page {
height: control.height * 0.3 height: control.height * 0.3
width: parent.width width: parent.width
Image { spacing: photoContainerItem.width * 0.1
id: photo
property bool ready: false
Item {
id: photoContainerItem
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: parent.height * 0.9 height: parent.height * 0.9
width: status === Image.Null || status === Image.Error ? 0:parent.width * 0.5 width: photo.status === Image.Null || photo.status === Image.Error ? 0:parent.width * 0.4
fillMode: Image.PreserveAspectFit RectangularGlow {
id: effect
visible: photo.ready
anchors.fill: photo
glowRadius: 1
opacity: 0.4
color: "black"
cornerRadius: photo.width * 0.2
}
source: widgetData["photo"] === undefined ? "":widgetData["photo"].replace("https", "http").replace("www.digitalrock.de", "egw.ifsc-climbing.org") Image {
asynchronous: true id: photo
property bool ready: photo.status === Image.Ready
FancyBusyIndicator {
height: width
anchors.centerIn: parent anchors.centerIn: parent
opacity: photo.status === Image.Loading 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
}
}
}
} }
} }
@ -99,7 +131,7 @@ Page {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: parent.height * 0.9 height: parent.height * 0.9
width: parent.width - photo.width width: parent.width - photoContainerItem.width * 1.1
Label { Label {
@ -167,7 +199,8 @@ Page {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: qsTr("age") + ": " + widgetData["age"] //% "Age"
text: qsTrId("#age") + ": " + widgetData["age"]
} }
Label { Label {
@ -183,7 +216,8 @@ Page {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: qsTr("year of birth") + ": " + widgetData["birthdate"] //% "Year of birth"
text: qsTrId("#yearOfBirth") + ": " + widgetData["birthdate"]
} }
Label { Label {
@ -199,7 +233,8 @@ Page {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: qsTr("city") + ": " + widgetData["city"] //% "City"
text: qsTrId("#city") + ": " + widgetData["city"]
} }
} }
@ -214,7 +249,9 @@ Page {
wrapMode: Label.Wrap wrapMode: Label.Wrap
text: widgetData["freetext"] === undefined ? "":widgetData["freetext"] text: widgetData["freetext"] === undefined ?
"":
(widgetData["freetext"] + "<br>")
} }
@ -224,9 +261,9 @@ Page {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
height: 1 height: 1
width: parent.width * 0.9 width: parent.width
color: "black" color: Material.foreground
} }
@ -238,7 +275,11 @@ Page {
flat: true flat: true
text: bestResultsRep.showAllResults ? qsTr("show best results"):qsTr("show all results") text: bestResultsRep.showAllResults ?
//% "Show best results"
qsTrId("#showBestResults"):
//% "Show all results"
qsTrId("#showAllResults")
onClicked: { onClicked: {
bestResultsRep.showAllResults = !bestResultsRep.showAllResults bestResultsRep.showAllResults = !bestResultsRep.showAllResults
@ -307,6 +348,7 @@ Page {
width: parent.width width: parent.width
height: 50 height: 50
spacing: width * 0.05
opacity: 0 opacity: 0
scale: 0.9 scale: 0.9
@ -324,11 +366,11 @@ Page {
Label { Label {
id: bestResultRankLa id: bestResultRankLa
width: parent.width * 0.2 width: parent.width * 0.1
height: parent.height height: parent.height
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignLeft
fontSizeMode: Text.Fit fontSizeMode: Text.Fit
minimumPixelSize: 1 minimumPixelSize: 1
@ -345,7 +387,7 @@ Page {
height: parent.height height: parent.height
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignLeft
fontSizeMode: Text.Fit fontSizeMode: Text.Fit
minimumPixelSize: height * 0.3 minimumPixelSize: height * 0.3
@ -370,7 +412,7 @@ Page {
scale: 0.8 scale: 0.8
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignRight
fontSizeMode: Text.Fit fontSizeMode: Text.Fit
minimumPixelSize: 1 minimumPixelSize: 1

View file

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

View file

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

View file

@ -17,13 +17,11 @@
*/ */
import QtQuick 2.9 import QtQuick 2.9
import QtQuick.Controls 2.5 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0 import QtGraphicalEffects 1.0
import QtQuick.Controls.Material 2.3 import QtQuick.Controls.Material 2.3
//import QtLocation 5.13 import QtPurchasing 1.12
import QtPurchasing 1.12
import "../Components" import "../Components"
@ -37,42 +35,44 @@ DataListView {
property bool titleIsPageTitle: true property bool titleIsPageTitle: true
property Component headerComponent: RowLayout { property Component headerComponent: RowLayout {
id: headerComponent
height: parent.height height: parent.height
spacing: 0 spacing: 0
ToolButton { layoutDirection: Qt.RightToLeft
id: flowToolBt
visible: speedFlowChart.enabled
enabled: control.widgetData['route_order'] === "-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 { ToolButton {
id: moreToolBt id: moreToolBt
Layout.alignment: Layout.Right onClicked: control.changeCat()
text: "\uf142"
font.family: fa5solid.name
}
ToolButton {
id: flowToolBt
visible: speedFlowChartPopup.enabled
enabled: control.widgetData['route_order'] === "-1" && Object.keys(control.widgetData['route_names']).length > 2
onClicked: { onClicked: {
control.changeCat() control.positionViewAtBeginning()
speedFlowChartPopup.toggle()
} }
icon.name: "menu" text: "\uf0e8"
font.family: fa5solid.name
}
ToolButton {
id: shareToolBt
onClicked: shareWidget(control.title)
text: "\uf1e0"
font.family: fa5solid.name
} }
} }
@ -105,36 +105,34 @@ DataListView {
console.log("widget data changed") console.log("widget data changed")
if(control.widgetData['discipline'] === 'speed' && control.widgetData['route_order'] === "-1" && Object.keys(control.widgetData['route_names']).length > 2){ if(control.widgetData['discipline'] === 'speed' && control.widgetData['route_order'] === "-1" && Object.keys(control.widgetData['route_names']).length > 2){
speedFlowChart.flowchartData = ({}) speedFlowChartPopup.flowchartData = ({})
speedFlowChart.enabled = true speedFlowChartPopup.flowchartData = control.widgetData
speedFlowChart.flowchartData = control.widgetData speedFlowChartPopup.enabled = true
} }
else { else {
speedFlowChart.enabled = false speedFlowChartPopup.flowchartData = ({})
speedFlowChart.flowchartData = ({}) speedFlowChartPopup.enabled = false
} }
} }
function onBackRequested() {
if(!speedFlowChartPopup.isVisible())
return true
speedFlowChartPopup.toggle()
return false
}
function getSubtitle() { function getSubtitle() {
var titleString var titleString = control.widgetData["route_name"]
for(var i = 0; i < control.widgetData["categorys"].length; i ++ ){ //% "(Results)"
//console.log("checking " + i + ": cat: " + parseInt(control.widgetData["categorys"][i]["GrpId"]) + " searched cat: " + params.cat) var addition = qsTrId("#resultsHeadline")
if(parseInt(control.widgetData["categorys"][i]["GrpId"]) === parseInt(params.cat)){
titleString = control.widgetData["categorys"][i]["name"]
}
}
var addition = qsTr("(Results)") if(titleString)
if(titleString !== undefined){
return addition + " " + titleString return addition + " " + titleString
} else
else {
return "" return ""
}
} }
function changeRoute(route) { function changeRoute(route) {
@ -158,12 +156,12 @@ DataListView {
} }
} }
selector.appear(selectOptions, qsTr("select cat")) selector.appear(selectOptions, qsTrId("#selectCategory"))
} }
Connections { Connections {
target: selector target: selector
onSelectionFinished: { function onSelectionFinished(index, data) {
if(data.cat !== undefined){ if(data.cat !== undefined){
updateData({cat: data.cat}, true) updateData({cat: data.cat}, true)
} }
@ -171,454 +169,13 @@ DataListView {
} }
onContentYChanged: { onContentYChanged: {
if(contentY > 0 && speedFlowChartBackgroundRect.state === "visible"){ if(contentY > 0 && speedFlowChartPopup.state === "visible"){
control.positionViewAtBeginning() control.positionViewAtBeginning()
} }
} }
delegate: ItemDelegate { delegate: ResultDelegate {
id: partDel enabled: speedFlowChartPopup.state === "hidden"
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 { footer: ItemDelegate {
@ -640,7 +197,7 @@ DataListView {
property var tabs: getTabs() property var tabs: getTabs()
property var tabIndexes: [] property var tabIndexes: []
enabled: speedFlowChartBackgroundRect.state === "hidden" enabled: speedFlowChartPopup.state === "hidden"
anchors { anchors {
left: parent.left left: parent.left
@ -720,195 +277,13 @@ DataListView {
} }
Rectangle { SpeedFlowChartPopup {
id: speedFlowChartBackgroundRect id: speedFlowChartPopup
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 { PullRefresher {
// has to be placed here again,
// to be on top of the speed flowchart
target: control target: control
postRefreshDelay: 0 postRefreshDelay: 0
@ -921,4 +296,5 @@ DataListView {
} }
} }
} }

View file

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

View file

@ -20,31 +20,37 @@ import QtQuick 2.9
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import QtQuick.Controls 2.4 import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.12
import QtPurchasing 1.12 import QtPurchasing 1.12
import com.itsblue.digitalRockRanking 1.0 import de.itsblue.blueROCK 1.0
import "./Pages" import "./Pages"
import "./Components" import "./Components"
import "./Widgets"
Window { Window {
visible: true visible: true
width: 540 width: 540
height: 960 height: 960
title: qsTr("blueROCK") title: "blueROCK"
Page { Page {
id: app id: app
property int errorCode: -1 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: // comp cats source:
// - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/icc_calendar.php
// - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/dav_calendar.php // - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/dav_calendar.php
// - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/sac_calendar.php // - https://github.com/ralfbecker/ranking/blob/master/sitemgr/digitalrock/sac_calendar.php
property var compCats: { property var compCats: {
// --- ICC --- // --- ICC ---
/*'int' : { /*'int' : {
@ -109,36 +115,10 @@ Window {
// --- GER --- // --- GER ---
/*'ger_boulder' : {
'label' : 'Bouldern',
'nation' : 'GER',
'wettk_reg' : '^[0-9]{2,2}_B+.*',
'serie_reg' : '^[0-9]{2,2}_BC',
'bgcolor' : '#FFDBA8', //'#f59d30'
'cat_id' : [59]
},
'ger' : {
'label' : 'Lead',
'nation' : 'GER',
'wettk_reg' : '^[0-9]{2,2}[_J]{1,1}[^WLJ]+.*',
'serie_reg' : '^[0-9]{2,2}_DC',
'bgcolor' : '#A8F0A8', //'#69b9a9'
'cat_id' : [57]
},
'ger_speed' : {
'label' : 'Speed',
'nation' : 'GER',
'wettk_reg' : '^[0-9]{2,2}[_J]{1,1}[^WLJ]+.*',
'serie_reg' : '',
'rang_title': '',
'bgcolor' : '#A8F0A8', //'#e72e5d'
'cat_id' : [60]
},*/
'ger_meisterschaft' : { 'ger_meisterschaft' : {
'label' : 'Deutsche Meisterschaft', 'label' : 'Deutsche Meisterschaft',
'nation' : 'GER', 'nation' : 'GER',
'bgcolor' : '#A8F0A8', 'bgcolor' : app.nationalAdultsColor,//'#A8F0A8',
'sort_rank': 1, 'sort_rank': 1,
'cat_id' : [57, 59, 60] 'cat_id' : [57, 59, 60]
}, },
@ -148,8 +128,8 @@ Window {
'nation' : 'GER', 'nation' : 'GER',
'wettk_reg' : '^[0-9]{2,2}[_J]{1,1}[^WL]+.*', 'wettk_reg' : '^[0-9]{2,2}[_J]{1,1}[^WL]+.*',
'serie_reg' : '^[0-9]{2,2}_JC', 'serie_reg' : '^[0-9]{2,2}_JC',
// 'rang_title': 'Deutsche Jugend RANGLISTE', // 'rang_title': 'Deutsche Jugend RANGLISTE',
'bgcolor' : '#D8FFD8', 'bgcolor' : app.nationalYouthColor,//'#D8FFD8',
'sort_rank': 2, 'sort_rank': 2,
'cat_id' : [58] 'cat_id' : [58]
}, },
@ -159,7 +139,7 @@ Window {
'wettk_reg' : '^[0-9]{2,2}[_J]{1,1}LM.*', 'wettk_reg' : '^[0-9]{2,2}[_J]{1,1}LM.*',
'serie_reg' : '^[0-9]{2,2}[_J]{1,1}LM.*', 'serie_reg' : '^[0-9]{2,2}[_J]{1,1}LM.*',
'rang_title': '', 'rang_title': '',
'bgcolor' : '#F0F0F0', 'bgcolor' : app.federalColor, //'#F0F0F0',
'sort_rank': 3, 'sort_rank': 3,
'cat_id' : [61,56] 'cat_id' : [61,56]
}, },
@ -172,7 +152,7 @@ Window {
'wettk_reg' : '^[0-9]{2,2}_[^R].*', 'wettk_reg' : '^[0-9]{2,2}_[^R].*',
'serie_reg' : '.*', 'serie_reg' : '.*',
'rang_title': 'SWISS RANKING', 'rang_title': 'SWISS RANKING',
'bgcolor' : '#A8F0A8', 'bgcolor' : app.nationalAdultsColor, //'#A8F0A8',
'sort_rank': 1, 'sort_rank': 1,
'cat_id' : [62,63] 'cat_id' : [62,63]
}, },
@ -182,7 +162,7 @@ Window {
'wettk_reg' : '^[0-9]{2,2}_[^R].*', 'wettk_reg' : '^[0-9]{2,2}_[^R].*',
'serie_reg' : '.*', 'serie_reg' : '.*',
'rang_title': 'SWISS RANKING', 'rang_title': 'SWISS RANKING',
'bgcolor' : '#D8FFD8', 'bgcolor' : app.nationalYouthColor, //'#D8FFD8',
'sort_rank': 2, 'sort_rank': 2,
'cat_id' : [65] 'cat_id' : [65]
}, },
@ -191,16 +171,16 @@ Window {
'nation' : 'SUI', 'nation' : 'SUI',
'wettk_reg' : '^[0-9]{2,2}_RG_.*', 'wettk_reg' : '^[0-9]{2,2}_RG_.*',
'rang_title': '', 'rang_title': '',
'bgcolor' : '#F0F0F0', 'bgcolor' : app.federalColor, //'#F0F0F0',
'sort_rank': 3, 'sort_rank': 3,
'cat_id' : [64] 'cat_id' : [64]
}, },
'sui_ice' : { 'sui_ice' : {
'label' : 'Iceclimbing', 'label' : 'Iceclimbing',
'nation' : 'SUI', 'nation' : 'SUI',
'wettk_reg' : '^[0-9]{2,2}_RC_.*', 'wparams["valid"]ettk_reg' : '^[0-9]{2,2}_RC_.*',
'rang_title': '', 'rang_title': '',
'bgcolor' : '#F0F0F0', 'bgcolor' : app.federalColor, //'#F0F0F0',
'sort_rank': 4, 'sort_rank': 4,
'cat_id' : [84] 'cat_id' : [84]
} }
@ -208,24 +188,30 @@ Window {
anchors.fill: parent anchors.fill: parent
Component.onCompleted: { Material.theme: appSettings.read("darkTheme") === "true" ? Material.Dark:Material.Light
//app.openAthlete(53139) // dorian: 53139 , rustam: 6933 , helen: 53300
//openWidget({nation:'GER'}) FontLoader {
//mainStack.push("Pages/AthleteSearchPage.qml") id: fa5solid
source: "qrc:/fonts/fa5solid.otf"
}
FontLoader {
id: fa5regular
source: "qrc:/fonts/fa5regular.otf"
} }
Shortcut { Shortcut {
sequences: ["Esc", "Back"] sequences: ["Esc", "Back"]
enabled: mainStack.depth > 1 enabled: mainStack.depth > 1
onActivated: { onActivated: app.goBack()
if(!mainStack.currentItem.locked){
mainStack.pop()
}
}
} }
ServerConn { BlueRockBackend {
id: serverConn id: serverConn
onOpenedViaUrl: {
app.openWidgetFromUrl(url)
}
} }
AppSettings { AppSettings {
@ -235,8 +221,6 @@ Window {
StackView { StackView {
id: mainStack id: mainStack
//enabled: !loadingDl.opened
anchors { anchors {
top: toolBar.bottom top: toolBar.bottom
left: parent.left left: parent.left
@ -313,13 +297,10 @@ Window {
height: parent.height height: parent.height
onClicked: { onClicked: app.goBack()
if(!mainStack.currentItem.locked){
mainStack.pop()
}
}
icon.name: "back" text: "\uf053"
font.family: fa5solid.name
} }
Column { Column {
@ -329,20 +310,16 @@ Window {
height: childrenRect.height height: childrenRect.height
width: parent.width - extraComponentLoader.width - toolButton.width - 3 * parent.spacing width: parent.width - extraComponentLoader.width - toolButton.width - 3 * parent.spacing
Label { MovingLabel {
id: toolBarTitleLa id: toolBarTitleLa
syncWithLabel: toolBarSubTitleLa
width: parent.width width: parent.width
scale: 1 scale: 1
elide: "ElideRight"
font.bold: true font.bold: true
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: "black"
text: getText() text: getText()
function getText(){ function getText(){
@ -371,18 +348,16 @@ Window {
} }
} }
Label { MovingLabel {
id: toolBarSubTitleLa id: toolBarSubTitleLa
width: parent.width visible: text !== ""
height: text !== "" ? undefined:0
elide: "ElideRight" syncWithLabel: toolBarTitleLa
width: parent.width
font.bold: false font.bold: false
color: "black"
text: getText() text: getText()
function getText(){ function getText(){
@ -392,7 +367,7 @@ Window {
titleString = mainStack.currentItem.subTitle titleString = mainStack.currentItem.subTitle
} }
return(titleString) return titleString
} }
Behavior on text { Behavior on text {
@ -411,28 +386,20 @@ Window {
height: parent.height height: parent.height
Layout.alignment: Layout.Right onItemChanged: {
if(item === null) {
onStatusChanged: {
//console.log("loader status changed: " + status)
if(status == 0){
extraComponentLoader.Layout.preferredWidth = 0 extraComponentLoader.Layout.preferredWidth = 0
} return
else {
extraComponentLoader.item.width = extraComponentLoader.item.implicitWidth
extraComponentLoader.Layout.preferredWidth = extraComponentLoader.item.width
widthChangedCon.target = extraComponentLoader.item
} }
//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
} }
//sourceComponent: mainStack.currentItem.headerComponent
Connections { Connections {
target: mainStack target: mainStack
onCurrentItemChanged: { function onCurrentItemChanged() {
secondCon.target = mainStack.currentItem secondCon.target = mainStack.currentItem
disappearNa.start() disappearNa.start()
} }
@ -440,14 +407,15 @@ Window {
Connections { Connections {
id: secondCon id: secondCon
onHeaderComponentChanged: { target: null
function onHeaderComponentChanged() {
disappearNa.start() disappearNa.start()
} }
} }
Connections { Connections {
id: widthChangedCon id: widthChangedCon
onImplicitWidthChanged:{ function onImplicitWidthChanged() {
extraComponentLoader.item.width = extraComponentLoader.item.implicitWidth extraComponentLoader.item.width = extraComponentLoader.item.implicitWidth
extraComponentLoader.Layout.preferredWidth = extraComponentLoader.item.implicitWidth extraComponentLoader.Layout.preferredWidth = extraComponentLoader.item.implicitWidth
} }
@ -543,7 +511,7 @@ Window {
] ]
} }
Dialog { Popup {
id: loadingDl id: loadingDl
x: ( app.width - width ) / 2 x: ( app.width - width ) / 2
@ -553,6 +521,9 @@ Window {
closePolicy: Dialog.NoAutoClose closePolicy: Dialog.NoAutoClose
contentItem: Column { contentItem: Column {
spacing: 50
FancyBusyIndicator { FancyBusyIndicator {
running: true running: true
} }
@ -562,16 +533,30 @@ Window {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
font.bold: true font.bold: true
color: "white"
text: "loading..." //% "Loading"
text: qsTrId("#loading") + "..."
} }
} }
background: Rectangle {
color: "transparent"
}
} }
Store { Store {
id: inAppProductStore
Product { Product {
id: speedFlowChartProduct id: speedFlowChartProduct
identifier: "speed_flowchart" identifier: "speed_flowchart"
//identifier: "android.test.purchased"
//identifier: "android.test.canceled"
//identifier: "android.test.refunded"
type: Product.Unlockable type: Product.Unlockable
onPurchaseRestored: { onPurchaseRestored: {
@ -588,28 +573,58 @@ Window {
} }
} }
function landscape(){ function landscape() {
return app.height < app.width return app.height < app.width
} }
function openWidget(params){ 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) {
loadingDl.open() loadingDl.open()
var calComp = Qt.createComponent("qrc:/Pages/WidgetPage.qml").createObject(null, {"params": params}) console.log("Opening widget: ", JSON.stringify(params))
app.errorCode = calComp.status
if(calComp.ready){ var result = false
mainStack.push(calComp)
} if(Object.keys(params).length) {
else { var calComp = Qt.createComponent("qrc:/Pages/WidgetPage.qml").createObject(null, {"params": params})
delete(calComp) app.errorCode = calComp.status
if(calComp.ready) {
mainStack.push(calComp)
result = true
}
else {
delete(calComp)
}
} }
loadingDl.close() loadingDl.close()
return result
} }
function defaultString(string, defaultString){ function openWidgetFromUrl(url) {
if(string === undefined || string === null){ var result = serverConn.getParamsFromUrl(url)
if(result["valid"]) {
openWidget(result["params"])
return app.errorCode !== 906
}
return result["valid"]
}
function defaultString(string, defaultString) {
if(string === undefined || string === null) {
return defaultString return defaultString
} }
else { else {
@ -617,6 +632,11 @@ Window {
} }
} }
function goBack() {
if(!mainStack.currentItem.hasOwnProperty('onBackRequested') || mainStack.currentItem.onBackRequested())
mainStack.pop()
}
function getErrorInfo(errorCode) { function getErrorInfo(errorCode) {
var infoLevel var infoLevel
@ -625,66 +645,36 @@ Window {
// 2 - error // 2 - error
var errorString var errorString
var errorDescription
switch(errorCode) { switch(errorCode) {
case 0: case 0:
infoLevel = 2 infoLevel = 2
errorString = "No connection to server" //% "No connection to server"
errorDescription = "Please check your internet connection and try again." errorString = qsTrId("#noConnectionError")
break break
case 200: case 404:
infoLevel = 0
errorString = "Success"
errorDescription = "The request was successfull"
break
case 401:
infoLevel = 2 infoLevel = 2
errorString = "Authentication required" //% "Not found"
errorDescription = "The server asked for user credentinals, please chack them and try again" errorString = qsTrId("#notFoundError")
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 break
case 901: case 901:
infoLevel = 1 infoLevel = 1
errorString = "No Data" //% "No Data"
errorDescription = "There is currently no data available. Please try again later." errorString = qsTrId("#noDataError")
break break
case 902: case 906:
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 infoLevel = 2
errorString = "Incompatible API" //% "Invalid Request"
errorDescription = "Please make shure that you are using the latest version of this app and try again." errorString = qsTrId("#invalidRequestError")
break errorDescription = "Invalid Request"
case 905:
infoLevel = 1
errorString = "Loading..."
errorDescription = "Please wait while we're loading some data"
break break
default: default:
infoLevel = 2 infoLevel = 2
errorString = "Unexpected error ("+errorCode+")" //% "Unexpected error"
errorDescription = "Unexpected error while getting data from the server. Please try again later." errorString = qsTrId("#unexpectedError") + " ("+errorCode+")"
} }
return([infoLevel, errorString, errorDescription]) return([infoLevel, errorString])
} }
} }

View file

@ -19,5 +19,21 @@
<file>Pages/AthleteSearchPage.qml</file> <file>Pages/AthleteSearchPage.qml</file>
<file>Components/SpeedFlowChart.qml</file> <file>Components/SpeedFlowChart.qml</file>
<file>Components/SwipeGallery.qml</file> <file>Components/SwipeGallery.qml</file>
<file>Components/CompetitionCalendarDelegate.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> </qresource>
</RCC> </RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

BIN
resources/shared/badge.xcf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 843 B

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