Compare commits
413 Commits
master
..
6ae396e605
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ae396e605 | |||
| ed2badfd35 | |||
| 1ff57b29f9 | |||
| c50a850ac9 | |||
| 15833f3685 | |||
| bf28a678c9 | |||
| 14090b96f4 | |||
| 8e6b1877cc | |||
| 9accb488f2 | |||
| 79a6d9a594 | |||
| 7d02e70459 | |||
| 4c190de479 | |||
| b36d1e02f5 | |||
| 53b290ab49 | |||
| b422430994 | |||
| 151678f0fe | |||
| cb2c38aaa1 | |||
| 00664c66a8 | |||
| 0ff5eb7bc9 | |||
| 3b8da1d3d6 | |||
| 9e139b5704 | |||
| c62a14645a | |||
| 3b1b0d0c19 | |||
| c32e64fe74 | |||
| 710e88d744 | |||
| 517e515ac1 | |||
| e8f0c4383c | |||
| b8cac73e74 | |||
| 95ef29fb09 | |||
| 86d12884fc | |||
| 50d2941e52 | |||
| 71506aab2d | |||
| 4e1272aba9 | |||
| 72ebe6f7e7 | |||
| 4b1d4379a0 | |||
| 2c376afd91 | |||
| 54ba04a7bd | |||
| 9b5a70b285 | |||
| 4f796dac2e | |||
| db9c3386f1 | |||
| bee5c02a4f | |||
| e8faa77e70 | |||
| 551c1bf1fa | |||
| 9973f12733 | |||
| 278fed52f1 | |||
| f89ac87c51 | |||
| 0b8380eff0 | |||
| 4ef3a167ea | |||
| c26cefc073 | |||
| e78ef0df49 | |||
| 0b6c80e84c | |||
| a52817231e | |||
| f6933b6529 | |||
| e4243e53ac | |||
| 0aead45191 | |||
| dacefd321b | |||
| 92a9a7358e | |||
| 174e6ac0b7 | |||
| c9eaed782a | |||
| 567184bcf9 | |||
| 541d6ef164 | |||
| 3469d02033 | |||
| 699aec8ab5 | |||
| 7a3a022ecd | |||
| 9d8a99df7c | |||
| a47e52e8e7 | |||
| a1fd21de04 | |||
| b3d8586c04 | |||
| 0409c5463f | |||
| 7a3b69fade | |||
| df275c0108 | |||
| 0525453d48 | |||
| 4e8b2f34f9 | |||
| bc6a069c90 | |||
| bfa0b0f5c0 | |||
| 274b77f705 | |||
| c7ea80bea9 | |||
| 1c787fdc4d | |||
| a689cf4fef | |||
| 8f92ab06d9 | |||
| b68bec9ebd | |||
| 81f65750b7 | |||
| 7b7ab2e82e | |||
| a460d2b296 | |||
| 33dd6c4c69 | |||
| 06c27d6b50 | |||
| 7dea44d1e8 | |||
| 5f27956035 | |||
| 32799f648c | |||
| 859b85ab2c | |||
| bd1101c348 | |||
| f29c84d05c | |||
| 7dbd6038f3 | |||
| 877633f4de | |||
| 22d3d18a17 | |||
| d65e61c297 | |||
| 467b0e0dd8 | |||
| 25a6ef37fa | |||
| 34763ace4a | |||
| 590a70c623 | |||
| 9b58412ca7 | |||
| a6c16e41c2 | |||
| 117434a5e3 | |||
| e4582eaac5 | |||
| 430d5b8dc7 | |||
| 9177c30d6e | |||
| 344f8f6d2c | |||
| 46971a8d46 | |||
| f330ef3f56 | |||
| 85f9988453 | |||
| f3de0bc165 | |||
| 421ee9179d | |||
| 0a66858d93 | |||
| 49428680de | |||
| 0c676dc3d6 | |||
| c702b610c5 | |||
| 5938c6b3c3 | |||
| 8000475c1f | |||
| da772f17cc | |||
| c44b0464a4 | |||
| 9d0cf8e313 | |||
| f0009dad88 | |||
| 905206f242 | |||
| 769fbc1b6a | |||
| 8daf57bcee | |||
| 33d488946a | |||
| 41a5e021c5 | |||
| 8f58893553 | |||
| 626d3d5564 | |||
| d833cdb733 | |||
| 8868914a76 | |||
| 70e6f82b10 | |||
| 6651613331 | |||
| 9ad0f624de | |||
| 82c143f847 | |||
| 1fdf731b81 | |||
| 2d3ccd25b4 | |||
| 385ee806d6 | |||
| 92aef41031 | |||
| 65b29ec4b8 | |||
| 9f51d68531 | |||
| 5bc4ba6332 | |||
| e9739ac2d5 | |||
| 4d3a33dd9b | |||
| ddeeaeaeac | |||
| c8e31b896b | |||
| c443a1d567 | |||
| 08ef784f57 | |||
| 999e30ab3a | |||
| c4c882a77d | |||
| fe93a94fc6 | |||
| a33c4ddac5 | |||
| 634fe41e78 | |||
| 328c4f410c | |||
| 43471fcf3d | |||
| e78e2b8cd4 | |||
| a57f42d4ed | |||
| 8ff993bf19 | |||
| 8968e53e5c | |||
| 430f2fe603 | |||
| d00592c3e7 | |||
| 3fb48b5bcf | |||
| 69fc98ad45 | |||
| 76a1f65083 | |||
| ed35e91d59 | |||
| ebbb70dc96 | |||
| 3281b134e0 | |||
| 2056be23cd | |||
| e57a1a915e | |||
| 181682a424 | |||
| 9fa711e460 | |||
| 1f30e2d97f | |||
| 0f84257eba | |||
| b171fef348 | |||
| 81f5b662b7 | |||
| 7a393bf630 | |||
| fc72391a75 | |||
| 6ad8203b6a | |||
| f58a2ec8cd | |||
| 91ef689d2a | |||
| ee6af2bc07 | |||
| 2db3b29359 | |||
| 107a5bdbf8 | |||
| 3802e4a5b9 | |||
| 04e8ce9c0a | |||
| 450c26b187 | |||
| 7129c0dee8 | |||
| ae6b6511d7 | |||
| 232a767312 | |||
| f5407d2477 | |||
| 19aca8f97f | |||
| c4a7533315 | |||
| d067ee702a | |||
| dfd9459922 | |||
| 51373fe7a2 | |||
| cafa1248d5 | |||
| ed710b3c2e | |||
| 8dd3ca9eae | |||
| 07ffa374fe | |||
| 0302c10fcd | |||
| 92361ec455 | |||
| cf4dea566e | |||
| d8c72a5d28 | |||
| 9803b06af1 | |||
| 8131ccae1e | |||
| e901f139d6 | |||
| 02dd659d1d | |||
| 277b3366f9 | |||
| b4defb9eda | |||
| 4c7f53e309 | |||
| 27618f4404 | |||
| 8bf5e5a06a | |||
| bc60362984 | |||
| 74c7c16877 | |||
| 0a577b5f48 | |||
| 57ddee2dc9 | |||
| 8ae8c97b41 | |||
| 883db8d7ef | |||
| 6450b292a7 | |||
| 65d15ffd4e | |||
| 07401d0864 | |||
| 21411e1517 | |||
| 339d402422 | |||
| c54a42aa43 | |||
| 42574583ed | |||
| 9aa3f7c058 | |||
| 1b407562df | |||
| f86c97dcf0 | |||
| 6b4d6a1c8c | |||
| 20dfe2c2e6 | |||
| 6c2c305f1c | |||
| dbbf6f6c5c | |||
| 07b32f1e32 | |||
| ea329d8d64 | |||
| f45848331e | |||
| a1ad6aa582 | |||
| 3e3e2579f0 | |||
| 681b5e42c3 | |||
| a0c025b58b | |||
| 20d7b16ede | |||
| afdc02f2a4 | |||
| 75846750f7 | |||
| 21e11b6c2a | |||
| 1de85c5820 | |||
| f771d3bcbb | |||
| e90b310be1 | |||
| 5134f50523 | |||
| 1374666858 | |||
| e7192008c0 | |||
| 0770ba49eb | |||
| b8e4dd4d07 | |||
| ba57ac4294 | |||
| 97b414412d | |||
| ac1ed1a2b0 | |||
| 010e2785fe | |||
| 90154880d0 | |||
| e40760a07a | |||
| a2f1ccae7b | |||
| 9cb3a93a51 | |||
| 15d550f55a | |||
| d368cfd5cd | |||
| 8b1e2fff1d | |||
| 7b3c0b4885 | |||
| eb361febf8 | |||
| b7a2f29f11 | |||
| 541f4249c1 | |||
| 57c2b5fd57 | |||
| f2829e4200 | |||
| efd13b0919 | |||
| 41372e9e86 | |||
| 948ee19bda | |||
| 6c3b99ffa4 | |||
| d3617f1402 | |||
| 1919ecbbb9 | |||
| 0b7908ec28 | |||
| 7e8e49c2dd | |||
| 3c29d5b956 | |||
| 9f467d079f | |||
| faf37ff19f | |||
| 3f779072e3 | |||
| 4a8e528cb8 | |||
| edb9b56637 | |||
| 2f1bb27360 | |||
| c2f05da96e | |||
| b18aa5a99e | |||
| 82586cb764 | |||
| 1825fde524 | |||
| cb8c65bec1 | |||
| e0f6ac2356 | |||
| 7bd6042bbf | |||
| 9411bfa2dd | |||
| 647c49e05e | |||
| 0f6c75690d | |||
| 22db412e75 | |||
| 3eae5ba10a | |||
| 64b23c88ae | |||
| be17c9a5ab | |||
| 0a37207b69 | |||
| 3681fcdae0 | |||
| da28a13268 | |||
| f7144884e3 | |||
| acb79bbc6f | |||
| 85c1ff0478 | |||
| 4433f1ba44 | |||
| d5344494d7 | |||
| 095b663bf1 | |||
| 151cc536ea | |||
| 7dee9f4f8a | |||
| 538ebd27bf | |||
| 6913c8ccd3 | |||
| e40fe4294c | |||
| 395d74ed4e | |||
| 8db1139d0d | |||
| 5532b4832c | |||
| df451032b4 | |||
| 9edbfd81af | |||
| b0bddb5cd7 | |||
| 591bbbeb45 | |||
| b99769e192 | |||
| dce569cb99 | |||
| 8e778e8cc3 | |||
| d033042e0b | |||
| 4e127f6ea7 | |||
| 2fcb787836 | |||
| 3c069d7f60 | |||
| 6d8a5af7cd | |||
| 4b5c2bf022 | |||
| 4475d50524 | |||
| 32211baee0 | |||
| c718f5eb07 | |||
| 52de843a64 | |||
| 22ab21ab3d | |||
| 2c655e56ec | |||
| bc37089dc6 | |||
| 9cc1edad23 | |||
| 132775e1bf | |||
| e1ceaa4249 | |||
| a479fc5ebd | |||
| 7b18fed51d | |||
| a237fba482 | |||
| 2ed68c4fe7 | |||
| ae9df32641 | |||
| 6d48d299b5 | |||
| 248483be4e | |||
| c4dae8df8c | |||
| 02dc1a1ecd | |||
| 258206a7a5 | |||
| eb5eec3576 | |||
| 18fb389210 | |||
| 460b771e27 | |||
| ef349685ef | |||
| 0b48c2e7ab | |||
| 3b673537e5 | |||
| 83b0cc18be | |||
| 35df3a50be | |||
| d4c0499e6d | |||
| ab2bead0b5 | |||
| fd77f2b558 | |||
| 734ff251aa | |||
| 6bc4d4d2ed | |||
| 482bf8dd0b | |||
| e01bb38af7 | |||
| 68bfe92849 | |||
| 7a411a34c9 | |||
| 7b4c698a8e | |||
| 9c1021a849 | |||
| fe685c40fc | |||
| 6816e77d21 | |||
| 5f0426aab9 | |||
| d63958574b | |||
| e378f8165f | |||
| 96fe8e61fc | |||
| a473adb10d | |||
| cbf049f6cd | |||
| 1b9aa58fb4 | |||
| 376472ab53 | |||
| f16f7efd45 | |||
| 06e665ce2e | |||
| c1c2c06873 | |||
| a73aea0986 | |||
| ca9cf687ea | |||
| 404c77b2cb | |||
| 1ff3b4f059 | |||
| 1a172d3d86 | |||
| 9c5b04cfc6 | |||
| 6c5d8bd8ec | |||
| ab8d61d392 | |||
| 21cd9da9a9 | |||
| 6f2875e619 | |||
| d6ebc43e5c | |||
| 98896fcef8 | |||
| bdb29029c9 | |||
| 5f9a1e7b5f | |||
| 0be070b460 | |||
| 7fe648ea77 | |||
| fa1717a053 | |||
| 5c76e75aa0 | |||
| 987734626c | |||
| 03a930cf83 | |||
| efa389fcaf | |||
| d234074b87 | |||
| 45a829082b | |||
| 86c3a397da | |||
| ddae68825c | |||
| ba7c4f54cf | |||
| 4af8030af9 | |||
| 4c956c9f33 | |||
| 3a55752df1 | |||
| eb7611433e | |||
| 62ae6a6e3c | |||
| 2edec5ca3c | |||
| 7a8f01536a | |||
| bdb5aaa606 |
@@ -3,7 +3,8 @@ name: update version
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- master
|
||||
- beta
|
||||
|
||||
jobs:
|
||||
increment-build-number:
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# ---> Dart
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/flutter,intellij,androidstudio,xcode,dart
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=flutter,intellij,androidstudio,xcode,dart
|
||||
|
||||
### Dart ###
|
||||
# See https://www.dartlang.org/guides/libraries/private-files
|
||||
|
||||
# Files and directories created by pub
|
||||
@@ -27,4 +30,323 @@ doc/api/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
|
||||
### Dart Patch ###
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
### Flutter ###
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
.fvm/flutter_sdk
|
||||
.pub-cache/
|
||||
.pub/
|
||||
coverage/
|
||||
lib/generated_plugin_registrant.dart
|
||||
# For library packages, don’t commit the pubspec.lock file.
|
||||
# Regenerating the pubspec.lock file lets you test your package against the latest compatible versions of its dependencies.
|
||||
# See https://dart.dev/guides/libraries/private-files#pubspeclock
|
||||
#pubspec.lock
|
||||
|
||||
# Android related
|
||||
materials/screenshots/android/**/gradle-wrapper.jar
|
||||
materials/screenshots/android/.gradle
|
||||
materials/screenshots/android/captures/
|
||||
materials/screenshots/android/gradlew
|
||||
materials/screenshots/android/gradlew.bat
|
||||
materials/screenshots/android/key.properties
|
||||
materials/screenshots/android/local.properties
|
||||
materials/screenshots/android/**/GeneratedPluginRegistrant.java
|
||||
|
||||
# iOS/XCode related
|
||||
**/ios/**/*.mode1v3
|
||||
**/ios/**/*.mode2v3
|
||||
**/ios/**/*.moved-aside
|
||||
**/ios/**/*.pbxuser
|
||||
**/ios/**/*.perspectivev3
|
||||
**/ios/**/*sync/
|
||||
**/ios/**/.sconsign.dblite
|
||||
**/ios/**/.tags*
|
||||
**/ios/**/.vagrant/
|
||||
**/ios/**/DerivedData/
|
||||
**/ios/**/Icon?
|
||||
**/ios/**/Pods/
|
||||
**/ios/**/.symlinks/
|
||||
**/ios/**/profile
|
||||
**/ios/**/xcuserdata
|
||||
**/ios/.generated/
|
||||
**/ios/Flutter/.last_build_id
|
||||
**/ios/Flutter/App.framework
|
||||
**/ios/Flutter/Flutter.framework
|
||||
**/ios/Flutter/Flutter.podspec
|
||||
**/ios/Flutter/Generated.xcconfig
|
||||
**/ios/Flutter/app.flx
|
||||
**/ios/Flutter/app.zip
|
||||
**/ios/Flutter/flutter_assets/
|
||||
**/ios/Flutter/flutter_export_environment.sh
|
||||
**/ios/ServiceDefinitions.json
|
||||
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!**/ios/**/default.mode1v3
|
||||
!**/ios/**/default.mode2v3
|
||||
!**/ios/**/default.pbxuser
|
||||
!**/ios/**/default.perspectivev3
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
|
||||
### Intellij ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Intellij Patch ###
|
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||
|
||||
# *.iml
|
||||
# modules.xml
|
||||
# .idea/misc.xml
|
||||
# *.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||
.idea/**/sonarlint/
|
||||
|
||||
# SonarQube Plugin
|
||||
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||
.idea/**/sonarIssues.xml
|
||||
|
||||
# Markdown Navigator plugin
|
||||
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||
.idea/**/markdown-navigator.xml
|
||||
.idea/**/markdown-navigator-enh.xml
|
||||
.idea/**/markdown-navigator/
|
||||
|
||||
# Cache file creation bug
|
||||
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||
.idea/$CACHE_FILE$
|
||||
|
||||
# CodeStream plugin
|
||||
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||
.idea/codestream.xml
|
||||
|
||||
# Azure Toolkit for IntelliJ plugin
|
||||
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||
.idea/**/azureSettings.xml
|
||||
|
||||
### Xcode ###
|
||||
## User settings
|
||||
xcuserdata/
|
||||
|
||||
## Xcode 8 and earlier
|
||||
*.xcscmblueprint
|
||||
*.xccheckout
|
||||
|
||||
### Xcode Patch ###
|
||||
*.xcodeproj/*
|
||||
!*.xcodeproj/project.pbxproj
|
||||
!*.xcodeproj/xcshareddata/
|
||||
!*.xcodeproj/project.xcworkspace/
|
||||
!*.xcworkspace/contents.xcworkspacedata
|
||||
/*.gcno
|
||||
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||
|
||||
### AndroidStudio ###
|
||||
# Covers files to be ignored for android development using Android Studio.
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
|
||||
# Gradle files
|
||||
.gradle
|
||||
.gradle/
|
||||
|
||||
# Signing files
|
||||
.signing/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio
|
||||
/*/build/
|
||||
/*/local.properties
|
||||
/*/out
|
||||
/*/*/build
|
||||
/*/*/production
|
||||
captures/
|
||||
.navigation/
|
||||
*.ipr
|
||||
*~
|
||||
*.swp
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Android Patch
|
||||
gen-external-apklibs
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
|
||||
# NDK
|
||||
obj/
|
||||
|
||||
# IntelliJ IDEA
|
||||
*.iml
|
||||
/out/
|
||||
|
||||
# User-specific configurations
|
||||
.idea/caches/
|
||||
.idea/libraries/
|
||||
.idea/shelf/
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/.name
|
||||
.idea/compiler.xml
|
||||
.idea/copyright/profiles_settings.xml
|
||||
.idea/encodings.xml
|
||||
.idea/misc.xml
|
||||
.idea/modules.xml
|
||||
.idea/scopes/scope_settings.xml
|
||||
.idea/dictionaries
|
||||
.idea/vcs.xml
|
||||
.idea/jsLibraryMappings.xml
|
||||
.idea/datasources.xml
|
||||
.idea/dataSources.ids
|
||||
.idea/sqlDataSources.xml
|
||||
.idea/dynamic.xml
|
||||
.idea/uiDesigner.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/gradle.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Legacy Eclipse project files
|
||||
.classpath
|
||||
.project
|
||||
.cproject
|
||||
.settings/
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
|
||||
hs_err_pid*
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
|
||||
# JIRA plugin
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/mongoSettings.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
|
||||
### AndroidStudio Patch ###
|
||||
|
||||
!/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/flutter,intellij,androidstudio,xcode,dart
|
||||
|
||||
*.idea*
|
||||
**/.DS_store
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
@@ -1 +0,0 @@
|
||||
client
|
||||
@@ -1,117 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
@@ -1,27 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Dart SDK">
|
||||
<CLASSES>
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/async" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/cli" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/collection" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/convert" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/core" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/developer" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/ffi" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/html" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/indexed_db" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/io" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/isolate" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/js" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/js_util" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/math" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/mirrors" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/svg" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/typed_data" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/web_audio" />
|
||||
<root url="file:///opt/flutter/bin/cache/dart-sdk/lib/web_gl" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,46 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Flutter Plugins" type="FlutterPluginsLibraryType">
|
||||
<CLASSES>
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/better_open_file-3.6.4" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/package_info-2.0.2" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/path_provider-2.0.15" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/device_info_plus-8.2.2" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_linux-3.0.5" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/path_provider_android-2.0.27" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_ios-6.1.4" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/image_picker_windows-0.2.1" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/file_selector_windows-0.9.3" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/image_picker_macos-0.2.1" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/file_selector_linux-0.9.2" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/image_picker_linux-0.2.1" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/syncfusion_pdfviewer_web-21.2.10" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_web-2.0.18" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/libphonenumber_plugin-0.3.2" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_macos-3.0.6" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/image_picker_ios-0.8.8" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher-6.1.12" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/libphonenumber_web-0.3.1" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/image_picker-1.0.1" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.3.0" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/syncfusion_flutter_pdfviewer-21.2.10" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/file_selector_macos-0.9.3+1" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/shared_preferences_web-2.2.0" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/path_provider_foundation-2.2.4" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/image_picker_for_web-2.2.0" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/shared_preferences_android-2.2.0" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_windows-3.0.7" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/flutter_native_splash-2.3.1" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/path_provider_windows-2.1.7" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.3.0" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/shared_preferences-2.2.0" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.3.2" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/path_provider_linux-2.1.11" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/file_picker-5.3.1" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_android-6.0.37" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/image_picker_android-0.8.7+4" />
|
||||
<root url="file://$USER_HOME$/.pub-cache/hosted/pub.dev/sqflite-2.3.0" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,15 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime">
|
||||
<CLASSES>
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect-sources.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,11 +0,0 @@
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="io.flutter" />
|
||||
</component>
|
||||
<component name="SuppressKotlinCodeStyleNotification">
|
||||
<option name="disableForAll" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/client.iml" filepath="$PROJECT_DIR$/client.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/android/client_android.iml" filepath="$PROJECT_DIR$/android/client_android.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,6 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
|
||||
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,11 +1,11 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled.
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
channel: stable
|
||||
revision: "ba393198430278b6595976de84fe170f553cc728"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
@@ -13,26 +13,26 @@ project_type: app
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: android
|
||||
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: ios
|
||||
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: linux
|
||||
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: macos
|
||||
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: web
|
||||
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: windows
|
||||
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
|
||||
# User provided section
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
[
|
||||
{
|
||||
|
||||
"authenticate": {
|
||||
"incoming": {
|
||||
|
||||
},
|
||||
|
||||
"outgoing": {
|
||||
"username": "Username",
|
||||
"password": "Password"
|
||||
}
|
||||
},
|
||||
|
||||
"serverInfo": {
|
||||
"incoming": {
|
||||
"name": "Server name",
|
||||
"owner": "Server owner",
|
||||
"version": "Server version",
|
||||
"legal": "Any legal advice or caution"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"talkContacts": {
|
||||
"incoming": [
|
||||
{
|
||||
"name": "Full display name",
|
||||
"last_message": "Preview of last message",
|
||||
"last_time": "Time of last message as Text",
|
||||
"profile_picture": "Avatar Image as URL",
|
||||
"unread_message": "Chat has unread messages"
|
||||
}
|
||||
],
|
||||
|
||||
"outgoing": {
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
"talkNotifications": {
|
||||
"incoming": {
|
||||
"amount": "Amount of notifications"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,77 @@
|
||||
# MarianumMobile Client
|
||||
|
||||
Flutter-App für die Schul-Community: Webuntis-Stundenplan, Nextcloud Talk + Files, Custom MHSL-Backend (Breaker, Custom Events, Push).
|
||||
|
||||
## Stack
|
||||
|
||||
- **Flutter** (Dart >= 3.8)
|
||||
- **State:** `flutter_bloc` + `hydrated_bloc` (persistente BLoCs pro Modul)
|
||||
- **Navigation:** `persistent_bottom_nav_bar_v2` mit zentraler `AppRoutes`-Klasse als Single Entry Point
|
||||
- **HTTP:** `dio`, lokales Caching via `localstore` (Generic `RequestCache<T>`)
|
||||
- **Calendar:** `syncfusion_flutter_calendar`
|
||||
- **Datum/Zeit:** `jiffy` – wird **nur** über die Extensions in `lib/extensions/date_time.dart` verwendet
|
||||
- **Code-Gen:** `freezed`, `json_serializable`
|
||||
|
||||
## Ordnerstruktur
|
||||
|
||||
```
|
||||
lib/
|
||||
├── api/ HTTP-Layer pro Backend (mhsl/, marianumcloud/, webuntis/, holidays/)
|
||||
├── state/app/modules/ BLoC pro Feature-Modul (timetable, chat, chat_list, files, ...)
|
||||
├── state/app/infrastructure LoadableState<T>, DataLoader, geteilte BLoC-Bausteine
|
||||
├── view/ Screens
|
||||
│ ├── login/ Login-Flow
|
||||
│ └── pages/ ein Verzeichnis pro Modul (timetable, files, talk, ...)
|
||||
├── widget/ Geteilte UI-Komponenten (Dialoge, Buttons, Sheets)
|
||||
├── extensions/ DateTime-, Text-, TimeOfDay-Extensions
|
||||
├── routing/ AppRoutes (Single Navigation Entry)
|
||||
├── theming/ Light/Dark Theme
|
||||
├── storage/ Freezed Settings-Modelle (HydratedBloc-persistent)
|
||||
├── notification/ Firebase + flutter_local_notifications
|
||||
└── utils/ Helper (clipboard_helper, debouncer, download_manager, ...)
|
||||
```
|
||||
|
||||
## Konventionen
|
||||
|
||||
**Navigation:** Ausschließlich über `AppRoutes.openX(context, ...)`. Direkte `Navigator.push(...)` für volle Pages sind nicht erlaubt – `Navigator.pop` für Sheets/Dialogs bleibt am Call-Site.
|
||||
|
||||
**Dialoge:**
|
||||
- Info/Fehler: `InfoDialog.show(context, body, copyable: true, title: '...')` aus `lib/widget/info_dialog.dart`.
|
||||
- Bestätigung: `ConfirmDialog(...).asDialog(context)` aus `lib/widget/confirm_dialog.dart`. Async-Bestätigung nutzt `onConfirmAsync` (zeigt Spinner und Inline-Fehler über `AsyncDialogAction`).
|
||||
- **Kein** inline `AlertDialog`/`SimpleDialog` mehr.
|
||||
|
||||
**Bottom-Sheets:** Detail-Sheets gehen über `showDetailsBottomSheet(context, header: ..., children: (ctx) => [...])` aus `lib/widget/details_bottom_sheet.dart`. Header ist optional.
|
||||
|
||||
**Async-Actions:** Statt manuelles Spinner+Try/Catch die `AsyncActionButton`-Familie aus `lib/widget/async_action_button.dart` (`AsyncActionButton`, `AsyncTextButton`, `AsyncIconButton`, `AsyncFab`, `AsyncListTile`, `AsyncDialogAction`, `runWithErrorDialog`). Fehler-Mapping läuft über `errorBuilder` oder zentral über `errorToUserMessage` aus `lib/api/errors/error_mapper.dart`.
|
||||
|
||||
**Clipboard:** Über `copyToClipboard(context, text)` aus `lib/utils/clipboard_helper.dart`. Zeigt automatisch SnackBar.
|
||||
|
||||
**Datum/Zeit-Formatierung:** Über die Extensions in `lib/extensions/date_time.dart`:
|
||||
`dt.formatHm()`, `dt.formatDate()`, `dt.formatDateTime()`, `dt.formatDateShort()`, `dt.formatRelative()`, `start.timeRangeTo(end)`. **Kein** direktes `Jiffy.parseFromDateTime(...).format(pattern: '...')` im View-Code.
|
||||
|
||||
**Settings:** Pro Feature ein Freezed-Modell unter `lib/storage/`, persistiert via HydratedBloc.
|
||||
|
||||
## Build / Run
|
||||
|
||||
```bash
|
||||
flutter pub get
|
||||
dart run build_runner build --delete-conflicting-outputs # nach Änderungen an Freezed/JSON-Modellen
|
||||
flutter run # Debug auf angeschlossenem Device
|
||||
flutter analyze # statische Analyse, muss 0 Issues melden
|
||||
flutter test # Tests (siehe test/)
|
||||
```
|
||||
|
||||
## Backend-Integrationen
|
||||
|
||||
| Backend | Pfad | Zweck |
|
||||
|---------------------------|-----------------------|----------------------------------------|
|
||||
| Webuntis | `lib/api/webuntis/` | Stundenplan, Klassen, Räume, Lehrer |
|
||||
| Nextcloud (Talk + WebDAV) | `lib/api/marianumcloud/` | Chats, Datei-Verwaltung |
|
||||
| Custom MHSL-Server | `lib/api/mhsl/` | Breaker, Custom Events, Notify, Noten |
|
||||
| Holiday-Calendar | `lib/api/holidays/` | Ferien |
|
||||
|
||||
`nextcloud`-Paket ist auf einen Custom-Fork gepinnt (siehe `pubspec.yaml` `dependency_overrides`).
|
||||
|
||||
## Tests
|
||||
|
||||
`test/` deckt aktuell nur Kern-Funktionen ab (DateTime-Extensions, AsyncActionController, LessonResolver). Beim Hinzufügen neuer pure-function-Helper bitte Test mit dazu.
|
||||
@@ -1,9 +1,61 @@
|
||||
MIT License
|
||||
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below.
|
||||
0. Additional Definitions.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
|
||||
|
||||
An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
|
||||
|
||||
A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”.
|
||||
|
||||
The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version.
|
||||
|
||||
The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or
|
||||
b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License.
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License.
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license document.
|
||||
c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
|
||||
d) Do one of the following:
|
||||
0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
|
||||
1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.
|
||||
e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License.
|
||||
b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.
|
||||
@@ -1,3 +1,18 @@
|
||||
# MarianumMobile
|
||||
Mobile client for Webuntis and Nextcloud with Talk integration
|
||||
Currently in early Alpha stage
|
||||
|
||||
MarianumMobile is a versatile and user-friendly mobile client designed to integrate Webuntis and Nextcloud functionalities, enriched with Talk and other integrations for a comprehensive communication experience.
|
||||
|
||||
## Features
|
||||
|
||||
- **Webuntis Integration**: Keep in time through the Webuntis integration. Access your timetables, schedules, and academic information on the go, directly from your mobile device.
|
||||
|
||||
- **Nextcloud Connectivity**: MarianumMobile covers the basic features by offering seamless Nextcloud integration. Access, your files, documents, photos, all within the app. Your Nextcloud becomes an extension of your mobile workspace.
|
||||
|
||||
- **Talk Integration**: Communication is essential in any environment. MarianumMobile helps with this need and incorporates a Talk integration. Stay connected with peers, teachers, and colleagues without leaving the app.
|
||||
|
||||
- **Cross-Platform Compatibility**: Our mobile client caters to both Android and iOS users, ensuring that no matter your device preference, MarianumMobile is readily available and accessible.
|
||||
|
||||
- **User-Friendly Interface**: Navigating through the app is a breeze thanks to its intuitive user interface. Whether you're checking your timetable, accessing files, or engaging in communication, MarianumMobile offers a seamless and user-friendly experience.
|
||||
|
||||
- **Offline Access**: Don't let a lack of internet connectivity hinder your productivity. With MarianumMobile, you can access previously synced data and files offline, ensuring you're always prepared.
|
||||
|
||||
|
||||
@@ -1,29 +1,88 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
# Static analysis configuration for the Flutter project.
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
# Base ruleset: flutter_lints (recommended Flutter defaults).
|
||||
# Additional lints below catch real bugs and enforce consistent style.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at
|
||||
# https://dart-lang.github.io/linter/lints/index.html.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
analyzer:
|
||||
language:
|
||||
strict-casts: true
|
||||
strict-raw-types: true
|
||||
errors:
|
||||
invalid_annotation_target: ignore
|
||||
todo: ignore
|
||||
exclude:
|
||||
- "**/*.g.dart"
|
||||
- "**/*.freezed.dart"
|
||||
- "lib/firebase_options.dart"
|
||||
- "build/**"
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
linter:
|
||||
rules:
|
||||
# === Project conventions ===
|
||||
prefer_relative_imports: true
|
||||
prefer_single_quotes: true
|
||||
eol_at_end_of_file: true
|
||||
omit_local_variable_types: true
|
||||
avoid_multiple_declarations_per_line: true
|
||||
|
||||
# === Bug catchers ===
|
||||
always_declare_return_types: true
|
||||
avoid_empty_else: true
|
||||
avoid_slow_async_io: true
|
||||
avoid_type_to_string: true
|
||||
avoid_void_async: true
|
||||
await_only_futures: true
|
||||
cancel_subscriptions: true
|
||||
cast_nullable_to_non_nullable: true
|
||||
close_sinks: true
|
||||
empty_catches: true
|
||||
hash_and_equals: true
|
||||
no_adjacent_strings_in_list: true
|
||||
no_duplicate_case_values: true
|
||||
test_types_in_equals: true
|
||||
throw_in_finally: true
|
||||
unawaited_futures: true
|
||||
unnecessary_statements: true
|
||||
unrelated_type_equality_checks: true
|
||||
use_build_context_synchronously: true
|
||||
valid_regexps: true
|
||||
|
||||
# === Flutter widget hygiene ===
|
||||
avoid_unnecessary_containers: true
|
||||
sized_box_for_whitespace: true
|
||||
sort_child_properties_last: true
|
||||
use_colored_box: true
|
||||
use_decorated_box: true
|
||||
use_full_hex_values_for_flutter_colors: true
|
||||
use_key_in_widget_constructors: true
|
||||
|
||||
# === Code clarity ===
|
||||
directives_ordering: true
|
||||
library_prefixes: true
|
||||
no_leading_underscores_for_local_identifiers: true
|
||||
prefer_conditional_assignment: true
|
||||
prefer_if_elements_to_conditional_expressions: true
|
||||
prefer_if_null_operators: true
|
||||
prefer_initializing_formals: true
|
||||
prefer_interpolation_to_compose_strings: true
|
||||
prefer_is_empty: true
|
||||
prefer_is_not_empty: true
|
||||
prefer_is_not_operator: true
|
||||
prefer_iterable_whereType: true
|
||||
prefer_null_aware_operators: true
|
||||
prefer_spread_collections: true
|
||||
prefer_void_to_null: true
|
||||
unnecessary_await_in_return: true
|
||||
unnecessary_brace_in_string_interps: true
|
||||
unnecessary_lambdas: true
|
||||
unnecessary_null_aware_assignments: true
|
||||
unnecessary_null_checks: true
|
||||
unnecessary_parenthesis: true
|
||||
unnecessary_string_interpolations: true
|
||||
use_super_parameters: true
|
||||
|
||||
# === File naming ===
|
||||
file_names: true
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="jbr-17" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/better_open_file-3.6.4/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/device_info_plus-8.2.2/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/file_picker-5.3.1/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/flutter_native_splash-2.3.1/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.15/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/image_picker_android-0.8.7+4/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/libphonenumber_plugin-0.3.2/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/package_info-2.0.2/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/path_provider_android-2.0.27/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/shared_preferences_android-2.2.0/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/sqflite-2.3.0/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/syncfusion_flutter_pdfviewer-21.2.10/android" />
|
||||
<option value="$USER_HOME$/.pub-cache/hosted/pub.dev/url_launcher_android-6.0.37/android" />
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="MavenRepo" />
|
||||
<option name="name" value="MavenRepo" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="Google8" />
|
||||
<option name="name" value="Google8" />
|
||||
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven" />
|
||||
<option name="name" value="maven" />
|
||||
<option name="url" value="https://storage.googleapis.com/download.flutter.io" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="BintrayJCenter" />
|
||||
<option name="name" value="BintrayJCenter" />
|
||||
<option name="url" value="https://jcenter.bintray.com/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.8.0" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,132 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="NONE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="aa1d4660-dd4d-4aab-a4e2-749864e3d02c" name="Changes" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/gradle.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/gradle.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[5200de3d4dd02295]" />
|
||||
<component name="ExternalProjectsData">
|
||||
<projectState path="$PROJECT_DIR$">
|
||||
<ProjectState />
|
||||
</projectState>
|
||||
</component>
|
||||
<component name="GenerateSignedApkSettings">
|
||||
<option name="EXPORT_PRIVATE_KEY" value="false" />
|
||||
<option name="KEY_STORE_PATH" value="$USER_HOME$/upload-keystore.jks" />
|
||||
<option name="KEY_ALIAS" value="upload" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
||||
</component>
|
||||
<component name="ProjectId" id="2TIxvwh2GdUl98Wy9jWCgaDdwAn" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"BundleExportedModule": "android.app",
|
||||
"ExportBundle.BundlePathForandroid.app": "/home/elias/projects/MarianumMobile/Client/android/app",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||
"cidr.known.project.marker": "true",
|
||||
"dart.analysis.tool.window.visible": "false",
|
||||
"last_opened_file_path": "/home/elias/upload-keystore.jks",
|
||||
"show.migrate.to.gradle.popup": "false"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"ExportApk.BuildVariants": [
|
||||
"release"
|
||||
]
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RunManager">
|
||||
<configuration name="app" type="AndroidRunConfigurationType" factoryName="Android App">
|
||||
<module name="android.app.main" />
|
||||
<option name="DEPLOY" value="true" />
|
||||
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||
<option name="ARTIFACT_NAME" value="" />
|
||||
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||
<option name="ALL_USERS" value="false" />
|
||||
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||
<option name="MODE" value="default_activity" />
|
||||
<option name="CLEAR_LOGCAT" value="false" />
|
||||
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
|
||||
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||
<Auto>
|
||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||
<option name="SHOW_STATIC_VARS" value="true" />
|
||||
<option name="WORKING_DIR" value="" />
|
||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||
</Auto>
|
||||
<Hybrid>
|
||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||
<option name="SHOW_STATIC_VARS" value="true" />
|
||||
<option name="WORKING_DIR" value="" />
|
||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||
</Hybrid>
|
||||
<Java>
|
||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||
</Java>
|
||||
<Native>
|
||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||
<option name="SHOW_STATIC_VARS" value="true" />
|
||||
<option name="WORKING_DIR" value="" />
|
||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
||||
</Native>
|
||||
<Profilers>
|
||||
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||
</Profilers>
|
||||
<option name="DEEP_LINK" value="" />
|
||||
<option name="ACTIVITY_CLASS" value="" />
|
||||
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="aa1d4660-dd4d-4aab-a4e2-749864e3d02c" name="Changes" comment="" />
|
||||
<created>1690744630092</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1690744630092</updated>
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,3 +1,9 @@
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
@@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
|
||||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
@@ -21,28 +22,32 @@ if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
ndkVersion flutter.ndkVersion
|
||||
namespace "eu.mhsl.marianum.mobile.client"
|
||||
compileSdk flutter.compileSdkVersion
|
||||
ndkVersion "28.2.13676358"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
coreLibraryDesugaringEnabled true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "eu.mhsl.marianum.mobile.client"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
minSdkVersion 26
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -57,3 +62,8 @@ android {
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.android.support:multidex:2.0.1'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "522850592536",
|
||||
"project_id": "marmobile-33b10",
|
||||
"storage_bucket": "marmobile-33b10.appspot.com"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:522850592536:android:9a355c61e6f1b0f0c2606d",
|
||||
"android_client_info": {
|
||||
"package_name": "eu.mhsl.marianum.mobile.client"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "522850592536-5urolovocke0fmr7kpd0hqvfd3gft6qo.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAXo66A3jSBxnAYKgpUIfucidELoHw5W3M"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "522850592536-5urolovocke0fmr7kpd0hqvfd3gft6qo.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "522850592536-edj90sbbnkjqe3aqui37j8enu93v4fk8.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "eu.mhsl.marianum.mobile.client",
|
||||
"app_store_id": "6458789560"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="eu.mhsl.marianum.mobile.client">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="eu.mhsl.marianum.mobile.client">
|
||||
<application
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="Marianum Fulda"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
@@ -24,12 +25,85 @@
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="application/*" />
|
||||
<data android:mimeType="text/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="application/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
<!-- Receiver classes live at the package root (NOT under .widgets) because
|
||||
the home_widget Flutter plugin resolves them as <app-package>.<name>. -->
|
||||
<receiver
|
||||
android:name="eu.mhsl.marianum.mobile.client.TimetableDayWidget"
|
||||
android:label="@string/widget_day_label"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/timetable_day_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="eu.mhsl.marianum.mobile.client.TimetableWeekWidget"
|
||||
android:label="@string/widget_week_label"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/timetable_week_widget_info" />
|
||||
</receiver>
|
||||
</application>
|
||||
<!-- Required so url_launcher / can_launch can actually see browsers,
|
||||
mail clients and dialers under Android 11+ package-visibility rules
|
||||
(otherwise UrlLauncher logs "component name for ... is null" and
|
||||
link taps in Talk silently do nothing). The PROCESS_TEXT intent is
|
||||
needed by io.flutter.plugin.text.ProcessTextPlugin (selection
|
||||
menu).
|
||||
See https://developer.android.com/training/package-visibility -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="http"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="mailto"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="tel"/>
|
||||
</intent>
|
||||
</queries>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<!-- Workmanager periodic widget refresh needs to reschedule after device
|
||||
reboot, otherwise the widget freezes until the user opens the app. -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
</manifest>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package eu.mhsl.marianum.mobile.client;
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Generated file.
|
||||
//
|
||||
// If you wish to remove Flutter's multidex support, delete this entire file.
|
||||
//
|
||||
// Modifications to this file should be done in a copy under a different name
|
||||
// as this file may be regenerated.
|
||||
|
||||
package io.flutter.app;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.multidex.MultiDex;
|
||||
|
||||
/**
|
||||
* Extension of {@link android.app.Application}, adding multidex support.
|
||||
*/
|
||||
public class FlutterMultiDexApplication extends Application {
|
||||
@Override
|
||||
@CallSuper
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
MultiDex.install(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.example.client
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity()
|
||||
@@ -0,0 +1,42 @@
|
||||
package eu.mhsl.marianum.mobile.client
|
||||
|
||||
import android.content.Intent
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
private val widgetChannel = "eu.mhsl.marianum.widget"
|
||||
/// Last seen widget tap target. Cleared by Dart via `consumePendingNavigation`
|
||||
/// so the same intent isn't replayed on every resume.
|
||||
private var pendingTimetableTap: Boolean = false
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
widgetChannel
|
||||
).setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"consumePendingNavigation" -> {
|
||||
val pending = pendingTimetableTap
|
||||
pendingTimetableTap = false
|
||||
result.success(pending)
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
consumeIntentData(intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
consumeIntentData(intent)
|
||||
}
|
||||
|
||||
private fun consumeIntentData(intent: Intent?) {
|
||||
if (intent?.getBooleanExtra("widget_open_timetable", false) == true) {
|
||||
pendingTimetableTap = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package eu.mhsl.marianum.mobile.client
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import eu.mhsl.marianum.mobile.client.widgets.WidgetRenderer
|
||||
|
||||
/**
|
||||
* Lives at the package root (not under `widgets/`) because the home_widget
|
||||
* Flutter plugin resolves the receiver class as `<app-package>.<androidName>`.
|
||||
*/
|
||||
class TimetableDayWidget : AppWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
) {
|
||||
for (id in appWidgetIds) {
|
||||
val options = appWidgetManager.getAppWidgetOptions(id)
|
||||
val views = WidgetRenderer.buildDay(context, context.packageName, options)
|
||||
appWidgetManager.updateAppWidget(id, views)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAppWidgetOptionsChanged(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetId: Int,
|
||||
newOptions: Bundle,
|
||||
) {
|
||||
// Re-render on resize, otherwise the tiles stay at install-time size
|
||||
// and either clip or leave dead space.
|
||||
val views = WidgetRenderer.buildDay(context, context.packageName, newOptions)
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package eu.mhsl.marianum.mobile.client
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import eu.mhsl.marianum.mobile.client.widgets.WidgetRenderer
|
||||
|
||||
class TimetableWeekWidget : AppWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
) {
|
||||
for (id in appWidgetIds) {
|
||||
val options = appWidgetManager.getAppWidgetOptions(id)
|
||||
val views = WidgetRenderer.buildWeek(context, context.packageName, options)
|
||||
appWidgetManager.updateAppWidget(id, views)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAppWidgetOptionsChanged(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetId: Int,
|
||||
newOptions: Bundle,
|
||||
) {
|
||||
val views = WidgetRenderer.buildWeek(context, context.packageName, newOptions)
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package eu.mhsl.marianum.mobile.client.widgets
|
||||
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
// Mirror of lib/widget_data/widget_data.dart — JSON keys + enum names
|
||||
// must stay in sync.
|
||||
enum class WidgetLessonStatus {
|
||||
REGULAR, ONGOING, PAST, CANCELLED, IRREGULAR, TEACHER_CHANGED, EVENT;
|
||||
|
||||
companion object {
|
||||
fun fromWire(raw: String?): WidgetLessonStatus = when (raw) {
|
||||
"regular" -> REGULAR
|
||||
"ongoing" -> ONGOING
|
||||
"past" -> PAST
|
||||
"cancelled" -> CANCELLED
|
||||
"irregular" -> IRREGULAR
|
||||
"teacherChanged" -> TEACHER_CHANGED
|
||||
"event" -> EVENT
|
||||
else -> REGULAR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class WidgetLesson(
|
||||
val start: Date,
|
||||
val end: Date,
|
||||
val subjectShort: String,
|
||||
val subjectLong: String?,
|
||||
val room: String?,
|
||||
val teacher: String?,
|
||||
val originalTeacher: String?,
|
||||
val status: WidgetLessonStatus,
|
||||
val customColor: String?,
|
||||
val siblingCount: Int,
|
||||
)
|
||||
|
||||
data class WidgetPeriod(
|
||||
val name: String,
|
||||
val startMinutes: Int,
|
||||
val endMinutes: Int,
|
||||
val virtualStartMinutes: Int,
|
||||
val virtualEndMinutes: Int,
|
||||
)
|
||||
|
||||
data class WidgetTimetableData(
|
||||
val fetchedAt: Date,
|
||||
val anchorDate: Date,
|
||||
val lessons: List<WidgetLesson>,
|
||||
val periods: List<WidgetPeriod>,
|
||||
val isHoliday: Boolean,
|
||||
val holidayName: String?,
|
||||
)
|
||||
|
||||
object WidgetDataParser {
|
||||
private val isoFormat: SimpleDateFormat
|
||||
get() = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ROOT).apply {
|
||||
timeZone = TimeZone.getDefault()
|
||||
}
|
||||
|
||||
/// Dart's toIso8601String() ships microseconds (6 digits) when non-zero;
|
||||
/// SimpleDateFormat only parses 3 → strip the extra digits. Local time
|
||||
/// without Z is the default, so removeSuffix("Z") makes the parser
|
||||
/// tolerate both shapes.
|
||||
private fun parseDate(raw: String?): Date? {
|
||||
if (raw.isNullOrEmpty()) return null
|
||||
val cleaned = raw
|
||||
.replace(Regex("([.,]\\d{3})\\d+"), "$1")
|
||||
.removeSuffix("Z")
|
||||
return try {
|
||||
isoFormat.parse(cleaned)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun parse(json: String?): WidgetTimetableData? {
|
||||
if (json.isNullOrEmpty()) return null
|
||||
return try {
|
||||
val root = JSONObject(json)
|
||||
val lessonsArray = root.optJSONArray("lessons")
|
||||
val lessons = mutableListOf<WidgetLesson>()
|
||||
if (lessonsArray != null) {
|
||||
for (i in 0 until lessonsArray.length()) {
|
||||
val obj = lessonsArray.optJSONObject(i) ?: continue
|
||||
val start = parseDate(obj.stringOrNull("start")) ?: continue
|
||||
val end = parseDate(obj.stringOrNull("end")) ?: continue
|
||||
lessons += WidgetLesson(
|
||||
start = start,
|
||||
end = end,
|
||||
subjectShort = obj.stringOrNull("subjectShort") ?: "",
|
||||
subjectLong = obj.stringOrNull("subjectLong"),
|
||||
room = obj.stringOrNull("room"),
|
||||
teacher = obj.stringOrNull("teacher"),
|
||||
originalTeacher = obj.stringOrNull("originalTeacher"),
|
||||
status = WidgetLessonStatus.fromWire(obj.stringOrNull("status")),
|
||||
customColor = obj.stringOrNull("customColor"),
|
||||
siblingCount = obj.optInt("siblingCount", 0),
|
||||
)
|
||||
}
|
||||
}
|
||||
val periodsArray = root.optJSONArray("periods")
|
||||
val periods = mutableListOf<WidgetPeriod>()
|
||||
if (periodsArray != null) {
|
||||
for (i in 0 until periodsArray.length()) {
|
||||
val obj = periodsArray.optJSONObject(i) ?: continue
|
||||
periods += WidgetPeriod(
|
||||
name = obj.stringOrNull("name") ?: "",
|
||||
startMinutes = obj.optInt("startMinutes", 0),
|
||||
endMinutes = obj.optInt("endMinutes", 0),
|
||||
virtualStartMinutes = obj.optInt("virtualStartMinutes", 0),
|
||||
virtualEndMinutes = obj.optInt("virtualEndMinutes", 0),
|
||||
)
|
||||
}
|
||||
}
|
||||
WidgetTimetableData(
|
||||
fetchedAt = parseDate(root.stringOrNull("fetchedAt")) ?: Date(),
|
||||
anchorDate = parseDate(root.stringOrNull("anchorDate")) ?: Date(),
|
||||
lessons = lessons,
|
||||
periods = periods,
|
||||
isHoliday = root.optBoolean("isHoliday", false),
|
||||
holidayName = root.stringOrNull("holidayName"),
|
||||
)
|
||||
} catch (_: JSONException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun JSONObject.stringOrNull(key: String): String? {
|
||||
if (!has(key) || isNull(key)) return null
|
||||
val raw = optString(key, "")
|
||||
return if (raw.isEmpty()) null else raw
|
||||
}
|
||||
}
|
||||
|
||||
object WidgetDateUtils {
|
||||
fun startOfDay(date: Date): Date {
|
||||
val cal = Calendar.getInstance().apply { time = date }
|
||||
cal.set(Calendar.HOUR_OF_DAY, 0)
|
||||
cal.set(Calendar.MINUTE, 0)
|
||||
cal.set(Calendar.SECOND, 0)
|
||||
cal.set(Calendar.MILLISECOND, 0)
|
||||
return cal.time
|
||||
}
|
||||
|
||||
fun isSameDay(a: Date, b: Date): Boolean {
|
||||
val ca = Calendar.getInstance().apply { time = a }
|
||||
val cb = Calendar.getInstance().apply { time = b }
|
||||
return ca.get(Calendar.YEAR) == cb.get(Calendar.YEAR) &&
|
||||
ca.get(Calendar.DAY_OF_YEAR) == cb.get(Calendar.DAY_OF_YEAR)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,786 @@
|
||||
package eu.mhsl.marianum.mobile.client.widgets
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import eu.mhsl.marianum.mobile.client.MainActivity
|
||||
import eu.mhsl.marianum.mobile.client.R
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* Renders the day and week widgets as a time-grid: lesson blocks absolutely
|
||||
* positioned on a vertical axis, mirroring the in-app Syncfusion calendar.
|
||||
* Per-hour dp is computed from the widget bundle so the grid scales with
|
||||
* resize, clamped to [MIN_HOUR_HEIGHT_DP, MAX_HOUR_HEIGHT_DP].
|
||||
*/
|
||||
object WidgetRenderer {
|
||||
private const val FALLBACK_VIRTUAL_MINUTES = 11 * 60
|
||||
|
||||
private const val DAY_CHROME_DP = 40
|
||||
private const val WEEK_CHROME_DP = 64
|
||||
|
||||
private const val MIN_HOUR_HEIGHT_DP = 18
|
||||
private const val MAX_HOUR_HEIGHT_DP = 72
|
||||
|
||||
private const val MIN_BLOCK_HEIGHT_DP = 16
|
||||
private const val LESSON_GAP_DP = 1.5f
|
||||
|
||||
// Below SHOW_ROOM_MIN: subject only. Below SHOW_TEACHER_SEPARATE_MIN:
|
||||
// subject + room. Above: subject + room + teacher stacked.
|
||||
private const val BLOCK_SHOW_ROOM_MIN_DP = 18
|
||||
private const val BLOCK_SHOW_TEACHER_SEPARATE_MIN_DP = 30
|
||||
|
||||
/// Below this column width autoSize can't fit subject + room — drop
|
||||
/// room/teacher entirely on the week-widget.
|
||||
private const val WEEK_COLUMN_TIGHT_DP = 45
|
||||
|
||||
private val timeFormat = SimpleDateFormat("HH:mm", Locale.GERMAN)
|
||||
private val dateShort = SimpleDateFormat("dd.MM.", Locale.GERMAN)
|
||||
private val weekdayShort = SimpleDateFormat("EE", Locale.GERMAN)
|
||||
private val dateTimeShort = SimpleDateFormat("dd.MM. HH:mm", Locale.GERMAN)
|
||||
|
||||
/// Hex values mirror LightAppTheme / DarkAppTheme tokens so the widget
|
||||
/// matches the app's branding rather than the generic system look.
|
||||
private data class WidgetPalette(
|
||||
val background: Int,
|
||||
val textPrimary: Int,
|
||||
val textSecondary: Int,
|
||||
val divider: Int,
|
||||
val breakBlock: Int,
|
||||
val watermarkAlpha: Float,
|
||||
)
|
||||
|
||||
private val lightPalette = WidgetPalette(
|
||||
background = 0xFFFCF7F5.toInt(),
|
||||
textPrimary = 0xFF1A1A1A.toInt(),
|
||||
textSecondary = 0xFF555555.toInt(),
|
||||
divider = 0x22000000,
|
||||
breakBlock = 0x0C000000,
|
||||
watermarkAlpha = 0.014f,
|
||||
)
|
||||
|
||||
private val darkPalette = WidgetPalette(
|
||||
background = 0xFF1F1716.toInt(),
|
||||
textPrimary = 0xFFF1F1F1.toInt(),
|
||||
textSecondary = 0xFFB0B0B0.toInt(),
|
||||
divider = 0x33FFFFFF,
|
||||
breakBlock = 0x14FFFFFF,
|
||||
watermarkAlpha = 0.025f,
|
||||
)
|
||||
|
||||
private fun resolvePalette(context: Context, themeMode: String?): WidgetPalette {
|
||||
val isDark = when (themeMode) {
|
||||
"light" -> false
|
||||
"dark" -> true
|
||||
else -> {
|
||||
val uiMode = context.resources.configuration.uiMode and
|
||||
Configuration.UI_MODE_NIGHT_MASK
|
||||
uiMode == Configuration.UI_MODE_NIGHT_YES
|
||||
}
|
||||
}
|
||||
return if (isDark) darkPalette else lightPalette
|
||||
}
|
||||
|
||||
fun buildDay(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
options: Bundle? = null,
|
||||
): RemoteViews {
|
||||
val prefs = sharedPrefs(context)
|
||||
val palette = resolvePalette(context, prefs.getString(KEY_THEME_MODE, "system"))
|
||||
if (!prefs.getBoolean(KEY_LOGGED_IN, false)) {
|
||||
return buildPlaceholder(
|
||||
context,
|
||||
packageName,
|
||||
context.getString(R.string.widget_login_required),
|
||||
palette,
|
||||
)
|
||||
}
|
||||
val data = WidgetDataParser.parse(prefs.getString(KEY_DAY_DATA, null))
|
||||
?: return buildPlaceholder(
|
||||
context,
|
||||
packageName,
|
||||
context.getString(R.string.widget_loading),
|
||||
palette,
|
||||
)
|
||||
|
||||
val totalVirtualMin = data.periods.lastOrNull()?.virtualEndMinutes
|
||||
?: FALLBACK_VIRTUAL_MINUTES
|
||||
val hourHeightDp = resolveHourHeight(options, DAY_CHROME_DP, totalVirtualMin)
|
||||
|
||||
val views = RemoteViews(packageName, R.layout.widget_day)
|
||||
applyChrome(views, palette, options)
|
||||
views.setTextColor(R.id.widget_day_title, palette.textPrimary)
|
||||
views.setTextColor(R.id.widget_day_subtitle, palette.textSecondary)
|
||||
views.setTextColor(R.id.widget_day_empty, palette.textSecondary)
|
||||
views.setTextViewText(
|
||||
R.id.widget_day_title,
|
||||
"${dayLabel(context, data.anchorDate)} · ${dateShort.format(data.anchorDate)}",
|
||||
)
|
||||
views.setTextViewText(
|
||||
R.id.widget_day_subtitle,
|
||||
context.getString(R.string.widget_status_label, freshnessLabel(context, data.fetchedAt)),
|
||||
)
|
||||
|
||||
views.removeAllViews(R.id.widget_day_time_labels)
|
||||
views.removeAllViews(R.id.widget_day_grid)
|
||||
|
||||
if (data.isHoliday) {
|
||||
views.setViewVisibility(R.id.widget_day_empty, View.VISIBLE)
|
||||
views.setTextViewText(
|
||||
R.id.widget_day_empty,
|
||||
data.holidayName ?: context.getString(R.string.widget_holiday),
|
||||
)
|
||||
} else if (data.lessons.isEmpty()) {
|
||||
views.setViewVisibility(R.id.widget_day_empty, View.VISIBLE)
|
||||
views.setTextViewText(
|
||||
R.id.widget_day_empty,
|
||||
context.getString(R.string.widget_no_lessons),
|
||||
)
|
||||
} else {
|
||||
views.setViewVisibility(R.id.widget_day_empty, View.GONE)
|
||||
populateGridLines(packageName, views, R.id.widget_day_time_labels, hourHeightDp, palette, data.periods)
|
||||
populateTimeLabels(packageName, views, R.id.widget_day_time_labels, hourHeightDp, palette, data.periods)
|
||||
populateGridLines(packageName, views, R.id.widget_day_grid, hourHeightDp, palette, data.periods)
|
||||
populateBreakBlocks(packageName, views, R.id.widget_day_grid, hourHeightDp, palette, data.periods)
|
||||
for (lesson in data.lessons) {
|
||||
addLessonBlock(
|
||||
context = context,
|
||||
packageName = packageName,
|
||||
parent = views,
|
||||
containerId = R.id.widget_day_grid,
|
||||
lesson = lesson,
|
||||
hourHeightDp = hourHeightDp,
|
||||
periods = data.periods,
|
||||
subjectOnly = false,
|
||||
horizontalPaddingDp = 7,
|
||||
)
|
||||
}
|
||||
maybeAddNowIndicator(
|
||||
packageName,
|
||||
views,
|
||||
R.id.widget_day_grid,
|
||||
hourHeightDp,
|
||||
anchorDate = data.anchorDate,
|
||||
periods = data.periods,
|
||||
)
|
||||
}
|
||||
|
||||
views.setOnClickPendingIntent(R.id.widget_root, openAppIntent(context))
|
||||
return views
|
||||
}
|
||||
|
||||
fun buildWeek(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
options: Bundle? = null,
|
||||
): RemoteViews {
|
||||
val prefs = sharedPrefs(context)
|
||||
val palette = resolvePalette(context, prefs.getString(KEY_THEME_MODE, "system"))
|
||||
if (!prefs.getBoolean(KEY_LOGGED_IN, false)) {
|
||||
return buildPlaceholder(
|
||||
context,
|
||||
packageName,
|
||||
context.getString(R.string.widget_login_required),
|
||||
palette,
|
||||
)
|
||||
}
|
||||
val data = WidgetDataParser.parse(prefs.getString(KEY_WEEK_DATA, null))
|
||||
?: return buildPlaceholder(
|
||||
context,
|
||||
packageName,
|
||||
context.getString(R.string.widget_loading),
|
||||
palette,
|
||||
)
|
||||
|
||||
val totalVirtualMin = data.periods.lastOrNull()?.virtualEndMinutes
|
||||
?: FALLBACK_VIRTUAL_MINUTES
|
||||
val hourHeightDp = resolveHourHeight(options, WEEK_CHROME_DP, totalVirtualMin)
|
||||
|
||||
val views = RemoteViews(packageName, R.layout.widget_week)
|
||||
applyChrome(views, palette, options)
|
||||
views.setTextColor(R.id.widget_week_title, palette.textPrimary)
|
||||
views.setTextColor(R.id.widget_week_subtitle, palette.textSecondary)
|
||||
val cal = Calendar.getInstance().apply { time = data.anchorDate }
|
||||
val weekNumber = cal.get(Calendar.WEEK_OF_YEAR)
|
||||
val end = Calendar.getInstance().apply {
|
||||
time = data.anchorDate
|
||||
add(Calendar.DAY_OF_YEAR, 4)
|
||||
}.time
|
||||
val kwPrefix = context.getString(R.string.widget_calendar_week_prefix)
|
||||
views.setTextViewText(
|
||||
R.id.widget_week_title,
|
||||
"$kwPrefix $weekNumber · ${dateShort.format(data.anchorDate)}–${dateShort.format(end)}",
|
||||
)
|
||||
views.setTextViewText(
|
||||
R.id.widget_week_subtitle,
|
||||
context.getString(R.string.widget_status_label, freshnessLabel(context, data.fetchedAt)),
|
||||
)
|
||||
|
||||
val headerIds = listOf(
|
||||
R.id.widget_week_header_mon,
|
||||
R.id.widget_week_header_tue,
|
||||
R.id.widget_week_header_wed,
|
||||
R.id.widget_week_header_thu,
|
||||
R.id.widget_week_header_fri,
|
||||
)
|
||||
val columnIds = listOf(
|
||||
R.id.widget_week_col_mon,
|
||||
R.id.widget_week_col_tue,
|
||||
R.id.widget_week_col_wed,
|
||||
R.id.widget_week_col_thu,
|
||||
R.id.widget_week_col_fri,
|
||||
)
|
||||
views.removeAllViews(R.id.widget_week_time_labels)
|
||||
populateGridLines(packageName, views, R.id.widget_week_time_labels, hourHeightDp, palette, data.periods)
|
||||
populateTimeLabels(packageName, views, R.id.widget_week_time_labels, hourHeightDp, palette, data.periods)
|
||||
|
||||
val (weekWidthDp, _) = widgetSizeDp(options)
|
||||
// Time-label column is 28dp wide; the rest is split across 5 days
|
||||
// plus thin dividers (negligible). Drop room/teacher only on the
|
||||
// very narrowest week widgets — autoSize handles the in-between
|
||||
// sizes.
|
||||
val dayColumnWidthDp = (weekWidthDp - 28f - 20f) / 5f
|
||||
val subjectOnly = dayColumnWidthDp < WEEK_COLUMN_TIGHT_DP
|
||||
|
||||
for ((index, columnId) in columnIds.withIndex()) {
|
||||
views.removeAllViews(headerIds[index])
|
||||
views.removeAllViews(columnId)
|
||||
val day = Calendar.getInstance().apply {
|
||||
time = data.anchorDate
|
||||
add(Calendar.DAY_OF_YEAR, index)
|
||||
}.time
|
||||
val header = RemoteViews(packageName, R.layout.widget_week_day_header)
|
||||
header.setTextColor(R.id.widget_week_day_header_weekday, palette.textPrimary)
|
||||
header.setTextColor(R.id.widget_week_day_header_date, palette.textSecondary)
|
||||
header.setTextViewText(R.id.widget_week_day_header_weekday, weekdayShort.format(day))
|
||||
header.setTextViewText(R.id.widget_week_day_header_date, dateShort.format(day))
|
||||
views.addView(headerIds[index], header)
|
||||
|
||||
populateGridLines(packageName, views, columnId, hourHeightDp, palette, data.periods)
|
||||
populateBreakBlocks(packageName, views, columnId, hourHeightDp, palette, data.periods)
|
||||
for (lesson in data.lessons.filter { WidgetDateUtils.isSameDay(it.start, day) }) {
|
||||
addLessonBlock(
|
||||
context = context,
|
||||
packageName = packageName,
|
||||
parent = views,
|
||||
containerId = columnId,
|
||||
lesson = lesson,
|
||||
hourHeightDp = hourHeightDp,
|
||||
periods = data.periods,
|
||||
subjectOnly = subjectOnly,
|
||||
horizontalPaddingDp = 3,
|
||||
)
|
||||
}
|
||||
if (WidgetDateUtils.isSameDay(day, Date())) {
|
||||
maybeAddNowIndicator(
|
||||
packageName,
|
||||
views,
|
||||
columnId,
|
||||
hourHeightDp,
|
||||
anchorDate = day,
|
||||
periods = data.periods,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
views.setOnClickPendingIntent(R.id.widget_root, openAppIntent(context))
|
||||
return views
|
||||
}
|
||||
|
||||
/// Pulls the launcher-reported widget size out of the AppWidget options
|
||||
/// bundle. The grid now spans `totalVirtualMin` minutes (lessons +
|
||||
/// preserved big breaks), so we divide by that instead of a fixed hour
|
||||
/// count to keep tiles readable across different timetables.
|
||||
private fun resolveHourHeight(
|
||||
options: Bundle?,
|
||||
chromeDp: Int,
|
||||
totalVirtualMin: Int,
|
||||
): Float {
|
||||
val virtualHours = (totalVirtualMin / 60.0f).coerceAtLeast(1f)
|
||||
val rawHeightDp = options?.let {
|
||||
max(
|
||||
it.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 0),
|
||||
it.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 0),
|
||||
)
|
||||
} ?: 0
|
||||
if (rawHeightDp <= 0) return 32f
|
||||
val gridHeightDp = (rawHeightDp - chromeDp)
|
||||
.coerceAtLeast((MIN_HOUR_HEIGHT_DP * virtualHours).toInt())
|
||||
return (gridHeightDp.toFloat() / virtualHours)
|
||||
.coerceIn(MIN_HOUR_HEIGHT_DP.toFloat(), MAX_HOUR_HEIGHT_DP.toFloat())
|
||||
}
|
||||
|
||||
/// Real wall-clock minute → position on the virtual axis. Inside a
|
||||
/// period: linear. In a gap: linear across the virtual gap (zero for
|
||||
/// squeezed small breaks, real width for big breaks).
|
||||
private fun realMinutesToVirtual(
|
||||
realMin: Int,
|
||||
periods: List<WidgetPeriod>,
|
||||
): Float {
|
||||
if (periods.isEmpty()) return realMin.toFloat()
|
||||
for (period in periods) {
|
||||
if (realMin in period.startMinutes..period.endMinutes) {
|
||||
return period.virtualStartMinutes + (realMin - period.startMinutes).toFloat()
|
||||
}
|
||||
}
|
||||
val first = periods.first()
|
||||
if (realMin < first.startMinutes) {
|
||||
return (realMin - first.startMinutes + first.virtualStartMinutes).toFloat()
|
||||
}
|
||||
val last = periods.last()
|
||||
if (realMin > last.endMinutes) {
|
||||
return last.virtualEndMinutes + (realMin - last.endMinutes).toFloat()
|
||||
}
|
||||
var prev = first
|
||||
for (i in 1 until periods.size) {
|
||||
val curr = periods[i]
|
||||
if (realMin in (prev.endMinutes + 1) until curr.startMinutes) {
|
||||
val gap = curr.startMinutes - prev.endMinutes
|
||||
val virtualGap = curr.virtualStartMinutes - prev.virtualEndMinutes
|
||||
return if (gap > 0) {
|
||||
prev.virtualEndMinutes +
|
||||
(realMin - prev.endMinutes).toFloat() * virtualGap / gap
|
||||
} else {
|
||||
curr.virtualStartMinutes.toFloat()
|
||||
}
|
||||
}
|
||||
prev = curr
|
||||
}
|
||||
return 0f
|
||||
}
|
||||
|
||||
/// Below this per-hour height the two-line label collapses to a single
|
||||
/// period number — time + number overlap otherwise.
|
||||
private const val TIME_LABEL_COMPACT_THRESHOLD_DP = 26f
|
||||
|
||||
private fun populateTimeLabels(
|
||||
packageName: String,
|
||||
parent: RemoteViews,
|
||||
containerId: Int,
|
||||
hourHeightDp: Float,
|
||||
palette: WidgetPalette,
|
||||
periods: List<WidgetPeriod>,
|
||||
) {
|
||||
val compact = hourHeightDp < TIME_LABEL_COMPACT_THRESHOLD_DP
|
||||
for (period in periods) {
|
||||
val label = RemoteViews(packageName, R.layout.widget_time_label)
|
||||
label.setTextViewText(R.id.widget_time_label_number, "${period.name}.")
|
||||
label.setTextViewText(R.id.widget_time_label_time, formatHm(period.startMinutes))
|
||||
if (compact) {
|
||||
label.setViewVisibility(R.id.widget_time_label_time, View.GONE)
|
||||
label.setTextViewTextSize(
|
||||
R.id.widget_time_label_number,
|
||||
TypedValue.COMPLEX_UNIT_SP,
|
||||
9f,
|
||||
)
|
||||
label.setTextColor(R.id.widget_time_label_number, palette.textPrimary)
|
||||
} else {
|
||||
label.setViewVisibility(R.id.widget_time_label_time, View.VISIBLE)
|
||||
label.setTextViewTextSize(
|
||||
R.id.widget_time_label_number,
|
||||
TypedValue.COMPLEX_UNIT_SP,
|
||||
7f,
|
||||
)
|
||||
label.setTextColor(R.id.widget_time_label_number, palette.textSecondary)
|
||||
}
|
||||
label.setTextColor(R.id.widget_time_label_time, palette.textPrimary)
|
||||
val topDp = period.virtualStartMinutes * hourHeightDp / 60.0f
|
||||
label.setViewLayoutMargin(
|
||||
R.id.widget_time_label_root,
|
||||
RemoteViews.MARGIN_TOP,
|
||||
topDp,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
)
|
||||
parent.addView(containerId, label)
|
||||
}
|
||||
}
|
||||
|
||||
private fun populateGridLines(
|
||||
packageName: String,
|
||||
parent: RemoteViews,
|
||||
containerId: Int,
|
||||
hourHeightDp: Float,
|
||||
palette: WidgetPalette,
|
||||
periods: List<WidgetPeriod>,
|
||||
) {
|
||||
// Lines at every period start + end, deduped by virtual minute so
|
||||
// adjacent periods share a line and big-break boundaries get both
|
||||
// upper and lower edges.
|
||||
val drawn = mutableSetOf<Int>()
|
||||
for (period in periods) {
|
||||
for (virtualMin in listOf(period.virtualStartMinutes, period.virtualEndMinutes)) {
|
||||
if (!drawn.add(virtualMin)) continue
|
||||
val line = RemoteViews(packageName, R.layout.widget_grid_line)
|
||||
line.setInt(R.id.widget_grid_line_root, "setBackgroundColor", palette.divider)
|
||||
val topDp = virtualMin * hourHeightDp / 60.0f
|
||||
line.setViewLayoutMargin(
|
||||
R.id.widget_grid_line_root,
|
||||
RemoteViews.MARGIN_TOP,
|
||||
topDp,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
)
|
||||
parent.addView(containerId, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Faint translucent block in any virtual gap between two periods —
|
||||
/// only big breaks (Hofpause, Mittagspause) survive the mapper's
|
||||
/// small-break collapse.
|
||||
private fun populateBreakBlocks(
|
||||
packageName: String,
|
||||
parent: RemoteViews,
|
||||
containerId: Int,
|
||||
hourHeightDp: Float,
|
||||
palette: WidgetPalette,
|
||||
periods: List<WidgetPeriod>,
|
||||
) {
|
||||
for (i in 0 until periods.size - 1) {
|
||||
val curr = periods[i]
|
||||
val next = periods[i + 1]
|
||||
val virtualGap = next.virtualStartMinutes - curr.virtualEndMinutes
|
||||
if (virtualGap <= 0) continue
|
||||
val block = RemoteViews(packageName, R.layout.widget_break_block)
|
||||
val topDp = curr.virtualEndMinutes * hourHeightDp / 60.0f
|
||||
val heightDp = virtualGap * hourHeightDp / 60.0f
|
||||
block.setViewLayoutMargin(
|
||||
R.id.widget_break_block_root,
|
||||
RemoteViews.MARGIN_TOP,
|
||||
topDp,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
)
|
||||
block.setViewLayoutHeight(
|
||||
R.id.widget_break_block_root,
|
||||
heightDp,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
)
|
||||
block.setInt(
|
||||
R.id.widget_break_block_root,
|
||||
"setBackgroundColor",
|
||||
palette.breakBlock,
|
||||
)
|
||||
parent.addView(containerId, block)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatHm(minutesSinceMidnight: Int): String {
|
||||
val h = minutesSinceMidnight / 60
|
||||
val m = minutesSinceMidnight % 60
|
||||
return "%02d:%02d".format(h, m)
|
||||
}
|
||||
|
||||
/// Overrides the chrome XML's `@color/widget_*` defaults when the user
|
||||
/// pins a fixed light/dark theme, and resizes the watermark M to match
|
||||
/// the current widget bounds.
|
||||
private fun applyChrome(views: RemoteViews, palette: WidgetPalette, options: Bundle?) {
|
||||
views.setInt(R.id.widget_root, "setBackgroundColor", palette.background)
|
||||
views.setInt(R.id.widget_watermark, "setColorFilter", palette.textPrimary)
|
||||
views.setFloat(R.id.widget_watermark, "setAlpha", palette.watermarkAlpha)
|
||||
|
||||
val (widthDp, heightDp) = widgetSizeDp(options)
|
||||
// Sized to the longer edge so the M scales with widget resize.
|
||||
// Negative end/bottom margin lets a sliver tuck behind the edge.
|
||||
val markSize = (max(widthDp, heightDp) * 0.8f).coerceIn(160f, 400f)
|
||||
val offsetEnd = -(markSize * 0.18f)
|
||||
val offsetBottom = -(markSize * 0.18f)
|
||||
views.setViewLayoutWidth(
|
||||
R.id.widget_watermark,
|
||||
markSize,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
)
|
||||
views.setViewLayoutHeight(
|
||||
R.id.widget_watermark,
|
||||
markSize,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
)
|
||||
views.setViewLayoutMargin(
|
||||
R.id.widget_watermark,
|
||||
RemoteViews.MARGIN_END,
|
||||
offsetEnd,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
)
|
||||
views.setViewLayoutMargin(
|
||||
R.id.widget_watermark,
|
||||
RemoteViews.MARGIN_BOTTOM,
|
||||
offsetBottom,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
)
|
||||
}
|
||||
|
||||
private fun widgetSizeDp(options: Bundle?): Pair<Int, Int> {
|
||||
if (options == null) return Pair(220, 220)
|
||||
val width = max(
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, 0),
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 0),
|
||||
).coerceAtLeast(140)
|
||||
val height = max(
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 0),
|
||||
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 0),
|
||||
).coerceAtLeast(140)
|
||||
return Pair(width, height)
|
||||
}
|
||||
|
||||
private fun addLessonBlock(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
parent: RemoteViews,
|
||||
containerId: Int,
|
||||
lesson: WidgetLesson,
|
||||
hourHeightDp: Float,
|
||||
periods: List<WidgetPeriod>,
|
||||
subjectOnly: Boolean,
|
||||
horizontalPaddingDp: Int,
|
||||
) {
|
||||
val cal = Calendar.getInstance()
|
||||
cal.time = lesson.start
|
||||
val startMinutes = cal.get(Calendar.HOUR_OF_DAY) * 60 + cal.get(Calendar.MINUTE)
|
||||
cal.time = lesson.end
|
||||
val endMinutes = cal.get(Calendar.HOUR_OF_DAY) * 60 + cal.get(Calendar.MINUTE)
|
||||
val durationMinutes = (endMinutes - startMinutes).coerceAtLeast(15)
|
||||
|
||||
val virtualStart = realMinutesToVirtual(startMinutes, periods)
|
||||
val virtualEnd = realMinutesToVirtual(startMinutes + durationMinutes, periods)
|
||||
if (virtualEnd <= virtualStart) return
|
||||
|
||||
// Half the gap above + half below so the grid line under the tile
|
||||
// stays visible.
|
||||
val topDp = virtualStart * hourHeightDp / 60.0f + LESSON_GAP_DP / 2f
|
||||
val heightDp = ((virtualEnd - virtualStart) * hourHeightDp / 60.0f - LESSON_GAP_DP)
|
||||
.coerceAtLeast(MIN_BLOCK_HEIGHT_DP.toFloat())
|
||||
|
||||
val block = RemoteViews(packageName, R.layout.widget_lesson_block)
|
||||
block.setViewLayoutMargin(
|
||||
R.id.widget_lesson_block_root,
|
||||
RemoteViews.MARGIN_TOP,
|
||||
topDp,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
)
|
||||
block.setViewLayoutHeight(
|
||||
R.id.widget_lesson_block_root,
|
||||
heightDp,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
)
|
||||
block.setInt(
|
||||
R.id.widget_lesson_block_root,
|
||||
"setBackgroundResource",
|
||||
statusDrawable(lesson),
|
||||
)
|
||||
|
||||
val density = context.resources.displayMetrics.density
|
||||
val padXPx = (horizontalPaddingDp * density).toInt()
|
||||
val padYPx = (3 * density).toInt()
|
||||
block.setViewPadding(
|
||||
R.id.widget_lesson_block_root,
|
||||
padXPx, padYPx, padXPx, padYPx,
|
||||
)
|
||||
|
||||
block.setTextViewText(R.id.widget_lesson_subject, subjectLabel(lesson))
|
||||
|
||||
// Separate fixed-size badge so the +N hint stays readable when
|
||||
// autoSize shrinks the subject on narrow tiles.
|
||||
if (lesson.siblingCount > 0) {
|
||||
block.setTextViewText(
|
||||
R.id.widget_lesson_sibling_badge,
|
||||
"+${lesson.siblingCount}",
|
||||
)
|
||||
block.setViewVisibility(R.id.widget_lesson_sibling_badge, View.VISIBLE)
|
||||
} else {
|
||||
block.setViewVisibility(R.id.widget_lesson_sibling_badge, View.GONE)
|
||||
}
|
||||
|
||||
val room = roomLabel(lesson)
|
||||
val teacher = teacherLabel(lesson)
|
||||
val noSecondaryContent = room.isNullOrEmpty() && teacher.isNullOrEmpty()
|
||||
val hideSecondary = subjectOnly ||
|
||||
heightDp < BLOCK_SHOW_ROOM_MIN_DP ||
|
||||
noSecondaryContent
|
||||
block.setViewVisibility(
|
||||
R.id.widget_lesson_secondary_stack,
|
||||
if (hideSecondary) View.GONE else View.VISIBLE,
|
||||
)
|
||||
// Custom-events have no room/teacher → let the subject wrap to 2 lines
|
||||
// so long titles don't autoshrink to nothing.
|
||||
block.setInt(
|
||||
R.id.widget_lesson_subject,
|
||||
"setMaxLines",
|
||||
if (noSecondaryContent) 2 else 1,
|
||||
)
|
||||
when {
|
||||
hideSecondary -> {
|
||||
applyOptionalText(block, R.id.widget_lesson_room, null)
|
||||
applyOptionalText(block, R.id.widget_lesson_teacher, null)
|
||||
}
|
||||
heightDp < BLOCK_SHOW_TEACHER_SEPARATE_MIN_DP -> {
|
||||
applyOptionalText(block, R.id.widget_lesson_room, room)
|
||||
applyOptionalText(block, R.id.widget_lesson_teacher, null)
|
||||
}
|
||||
else -> {
|
||||
applyOptionalText(block, R.id.widget_lesson_room, room)
|
||||
applyOptionalText(block, R.id.widget_lesson_teacher, teacher)
|
||||
}
|
||||
}
|
||||
|
||||
parent.addView(containerId, block)
|
||||
}
|
||||
|
||||
private fun applyOptionalText(views: RemoteViews, viewId: Int, text: String?) {
|
||||
if (text.isNullOrEmpty()) {
|
||||
views.setViewVisibility(viewId, View.GONE)
|
||||
} else {
|
||||
views.setTextViewText(viewId, text)
|
||||
views.setViewVisibility(viewId, View.VISIBLE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeAddNowIndicator(
|
||||
packageName: String,
|
||||
parent: RemoteViews,
|
||||
containerId: Int,
|
||||
hourHeightDp: Float,
|
||||
anchorDate: Date,
|
||||
periods: List<WidgetPeriod>,
|
||||
) {
|
||||
if (!WidgetDateUtils.isSameDay(anchorDate, Date())) return
|
||||
val now = Calendar.getInstance()
|
||||
val nowMinutes = now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE)
|
||||
if (periods.isNotEmpty()) {
|
||||
if (nowMinutes < periods.first().startMinutes ||
|
||||
nowMinutes > periods.last().endMinutes
|
||||
) return
|
||||
}
|
||||
val virtualNow = realMinutesToVirtual(nowMinutes, periods)
|
||||
val topDp = virtualNow * hourHeightDp / 60.0f
|
||||
val indicator = RemoteViews(packageName, R.layout.widget_now_indicator)
|
||||
indicator.setViewLayoutMargin(
|
||||
R.id.widget_now_indicator_root,
|
||||
RemoteViews.MARGIN_TOP,
|
||||
topDp,
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
)
|
||||
parent.addView(containerId, indicator)
|
||||
}
|
||||
|
||||
/// Custom-events use the user-picked palette (orange/red/green/blue,
|
||||
/// mirroring CustomTimetableColors).
|
||||
private fun statusDrawable(lesson: WidgetLesson): Int {
|
||||
if (lesson.status == WidgetLessonStatus.EVENT && lesson.customColor != null) {
|
||||
return when (lesson.customColor) {
|
||||
"orange" -> R.drawable.widget_lesson_block_event_orange
|
||||
"red" -> R.drawable.widget_lesson_block_event_red
|
||||
"green" -> R.drawable.widget_lesson_block_event_green
|
||||
"blue" -> R.drawable.widget_lesson_block_event_blue
|
||||
else -> R.drawable.widget_lesson_block_event_orange
|
||||
}
|
||||
}
|
||||
return when (lesson.status) {
|
||||
WidgetLessonStatus.CANCELLED -> R.drawable.widget_lesson_block_cancelled
|
||||
WidgetLessonStatus.IRREGULAR -> R.drawable.widget_lesson_block_irregular
|
||||
WidgetLessonStatus.TEACHER_CHANGED -> R.drawable.widget_lesson_block_teacher_changed
|
||||
WidgetLessonStatus.PAST -> R.drawable.widget_lesson_block_past
|
||||
WidgetLessonStatus.EVENT -> R.drawable.widget_lesson_block_event_orange
|
||||
WidgetLessonStatus.ONGOING -> R.drawable.widget_lesson_block_ongoing
|
||||
WidgetLessonStatus.REGULAR -> R.drawable.widget_lesson_block_regular
|
||||
}
|
||||
}
|
||||
|
||||
private fun subjectLabel(lesson: WidgetLesson): String {
|
||||
return lesson.subjectShort.ifEmpty { lesson.subjectLong ?: "—" }
|
||||
}
|
||||
|
||||
private fun roomLabel(lesson: WidgetLesson): String? = lesson.room
|
||||
|
||||
private fun teacherLabel(lesson: WidgetLesson): String? =
|
||||
lesson.teacher ?: lesson.originalTeacher
|
||||
|
||||
private fun dayLabel(context: Context, anchor: Date): String {
|
||||
val today = WidgetDateUtils.startOfDay(Date())
|
||||
val tomorrow = Calendar.getInstance().apply {
|
||||
time = today
|
||||
add(Calendar.DAY_OF_YEAR, 1)
|
||||
}.time
|
||||
val anchorStart = WidgetDateUtils.startOfDay(anchor)
|
||||
return when {
|
||||
anchorStart == today -> context.getString(R.string.widget_today)
|
||||
anchorStart == tomorrow -> context.getString(R.string.widget_tomorrow)
|
||||
else -> SimpleDateFormat("EEEE", Locale.GERMAN).format(anchor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun freshnessLabel(context: Context, fetchedAt: Date): String {
|
||||
val today = WidgetDateUtils.startOfDay(Date())
|
||||
val fetchedDay = WidgetDateUtils.startOfDay(fetchedAt)
|
||||
val yesterday = Calendar.getInstance().apply {
|
||||
time = today
|
||||
add(Calendar.DAY_OF_YEAR, -1)
|
||||
}.time
|
||||
val yesterdayPrefix = context.getString(R.string.widget_yesterday_prefix)
|
||||
return when (fetchedDay) {
|
||||
today -> timeFormat.format(fetchedAt)
|
||||
yesterday -> "$yesterdayPrefix ${timeFormat.format(fetchedAt)}"
|
||||
else -> dateTimeShort.format(fetchedAt)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildPlaceholder(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
message: String,
|
||||
palette: WidgetPalette,
|
||||
): RemoteViews {
|
||||
val views = RemoteViews(packageName, R.layout.widget_placeholder)
|
||||
applyChrome(views, palette, null)
|
||||
views.setTextColor(R.id.widget_placeholder_title, palette.textPrimary)
|
||||
views.setTextColor(R.id.widget_placeholder_message, palette.textSecondary)
|
||||
views.setTextViewText(
|
||||
R.id.widget_placeholder_title,
|
||||
context.getString(R.string.widget_placeholder_title),
|
||||
)
|
||||
views.setTextViewText(R.id.widget_placeholder_message, message)
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.widget_placeholder_message,
|
||||
openAppIntent(context),
|
||||
)
|
||||
return views
|
||||
}
|
||||
|
||||
private fun openAppIntent(context: Context): PendingIntent {
|
||||
// ACTION_MAIN + LAUNCHER mirrors a launcher tap; the boolean extra
|
||||
// is consumed by Dart via WidgetNavigation to route to the timetable.
|
||||
val intent = Intent(context, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
action = Intent.ACTION_MAIN
|
||||
addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
putExtra("widget_open_timetable", true)
|
||||
}
|
||||
return PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
}
|
||||
|
||||
private fun sharedPrefs(context: Context): SharedPreferences {
|
||||
return context.getSharedPreferences(
|
||||
"HomeWidgetPreferences",
|
||||
Context.MODE_PRIVATE,
|
||||
)
|
||||
}
|
||||
|
||||
const val KEY_DAY_DATA = "widget_data_day_v1"
|
||||
const val KEY_WEEK_DATA = "widget_data_week_v1"
|
||||
const val KEY_LOGGED_IN = "widget_data_logged_in_v1"
|
||||
const val KEY_THEME_MODE = "widget_setting_theme_mode_v1"
|
||||
}
|
||||
|
After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 70 B After Width: | Height: | Size: 69 B |
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Plain rounded rectangle. We deliberately avoid ?attr/appWidgetRadius
|
||||
because RemoteViews inflation does not resolve custom theme attributes
|
||||
reliably across launchers. Hard-coded 20dp matches the system home-screen
|
||||
radius closely on stock Android. -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="20dp" />
|
||||
<solid android:color="@color/widget_background" />
|
||||
</shape>
|
||||
|
Before Width: | Height: | Size: 70 B After Width: | Height: | Size: 69 B |
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Direct port of /home/elias/Bilder/marianum_m_white.svg.
|
||||
- viewport matches the source SVG (70mm × 82.2mm).
|
||||
- Transforms recreate the SVG's three nested matrices: outer scale+offset,
|
||||
inner translate, and a final scale+y-flip that pulls the path data into
|
||||
the visible coordinate space.
|
||||
- Tinted at runtime via setColorFilter so light/dark themes can share one
|
||||
drawable.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="70dp"
|
||||
android:height="82dp"
|
||||
android:viewportWidth="70.000168"
|
||||
android:viewportHeight="82.227348">
|
||||
|
||||
<group
|
||||
android:scaleX="0.26458333"
|
||||
android:scaleY="0.26458334"
|
||||
android:translateX="107.44411"
|
||||
android:translateY="-80.482198">
|
||||
<group
|
||||
android:translateX="-749.41293"
|
||||
android:translateY="290.52252">
|
||||
<group
|
||||
android:scaleX="0.13333333"
|
||||
android:scaleY="-0.13333333"
|
||||
android:translateX="0"
|
||||
android:translateY="632.14667">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M 3499.67,4594.33 c -16.43,-106.19 -29.71,-199.97 -43.79,-293.49 86.83,-19 138.5,-27.61 223.38,-43.82 63.81,-12.18 175.24,-20.4 179.64,-83.23 6.46,-92.69 -124.69,-55.41 -188.38,-43.81 -84.33,15.36 -159.13,28.84 -232.2,43.81 -13.68,-60.72 -26.83,-118.68 -39.43,-179.61 -36.76,-178.32 -73.67,-368.16 -105.11,-551.97 18.09,25.66 30.84,42.72 43.8,65.7 66.7,118.26 140.39,245.04 227.76,354.83 33.49,42.05 76.86,94.81 118.31,113.91 98.42,45.36 166.68,-22.2 170.87,-118.28 3.68,-85.28 -23.09,-181.17 -35.08,-275.99 -12.4,-98.19 -22.89,-194.93 -35.03,-275.98 72.44,102.69 147.93,269.64 240.95,381.12 27.51,33 73.55,80.61 118.27,87.62 218.76,34.33 126.58,-312.17 127.05,-473.13 0.4,-144.9 44.01,-255.37 175.21,-271.59 43.02,-5.31 105.84,11.16 112.7,-26.34 8.67,-47.38 -78.15,-60.52 -125.84,-61.28 -291.34,-4.51 -322.06,262.33 -311.01,573.88 -19.85,-18.57 -35.71,-47.53 -52.57,-74.47 -97.59,-155.88 -203.95,-327.22 -297.92,-503.79 -25.93,-48.79 -53.68,-114.7 -135.8,-83.23 -17.27,6.63 -48.25,44.39 -52.56,56.96 -19.58,57.19 1.55,137.42 8.76,205.89 21.54,203.72 57.81,389.09 78.87,587.01 -26.3,0.51 -43.93,-30.07 -56.96,-48.2 -46.9,-65.27 -86.02,-140.76 -127.04,-214.64 -52.84,-95.15 -108.23,-192.84 -157.71,-293.52 -75.25,-153.09 -188.6,-501.89 -242.12,-678.81 -8.67,-28.67 -17.7,-58.08 -26.3,-87.64 -7.48,-25.72 -10.39,-57.68 -35.05,-74.46 -100.02,18.93 -89.71,104.89 -70.09,205.9 47.35,243.43 170.89,706.45 211.48,946.04 -72.97,-70.97 -153.99,-207.41 -289.14,-236.55 -136.47,-29.44 -217.95,47.68 -271.6,122.66 -17.14,23.96 -41.43,49.54 -26.29,78.84 83.96,35.51 113.37,-65.2 197.15,-74.47 22.65,-2.5 54.56,2.4 74.46,8.78 132.4,42.34 237.57,218.76 297.87,346.07 74.16,156.45 125.32,330.5 148.95,490.64 -65.71,11.4 -142.96,22.15 -219.25,36.52 -109.8,20.72 -158.81,10.75 -201.29,59.86 9.15,41.95 41.41,60.8 70.1,83.24 126.26,-16.84 252.45,-33.77 372.36,-56.97 20.43,89.25 51.98,218.51 74.45,311.05 40.53,26.02 88.88,-8.43 105.17,-35.06 z" />
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/widget_divider" />
|
||||
</shape>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<corners android:radius="6dp" />
|
||||
<solid android:color="@color/widget_lesson_cancelled" />
|
||||
<stroke android:width="1.5dp" android:color="#C8FF0000" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:left="3dp"
|
||||
android:top="3dp"
|
||||
android:right="3dp"
|
||||
android:bottom="3dp"
|
||||
android:drawable="@drawable/widget_lesson_cancelled_x" />
|
||||
</layer-list>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="6dp" />
|
||||
<solid android:color="#FF2196F3" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="6dp" />
|
||||
<solid android:color="#FF4CAF50" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="6dp" />
|
||||
<solid android:color="#FFEF6C00" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="6dp" />
|
||||
<solid android:color="#FF993333" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="6dp" />
|
||||
<solid android:color="@color/widget_lesson_irregular" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="6dp" />
|
||||
<solid android:color="@color/widget_lesson_ongoing" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="6dp" />
|
||||
<solid android:color="@color/widget_lesson_past" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="6dp" />
|
||||
<solid android:color="@color/widget_lesson_regular" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="6dp" />
|
||||
<solid android:color="@color/widget_lesson_teacher_changed" />
|
||||
</shape>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportWidth="100"
|
||||
android:viewportHeight="100">
|
||||
<path
|
||||
android:strokeColor="#C8FF0000"
|
||||
android:strokeWidth="5"
|
||||
android:pathData="M0,0 L100,100 M100,0 L0,100" />
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FFE53935" />
|
||||
</shape>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/widget_break_block_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="14dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp" />
|
||||
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- FrameLayout root so the Marianum watermark can sit at bottom|end behind
|
||||
the actual widget content without disturbing the LinearLayout flow. -->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/widget_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/app_widget_background">
|
||||
|
||||
<!-- Bottom-leading so the mark looks like it's peeking out of the lower
|
||||
left corner. Width/height/start+bottom margins are overridden in the
|
||||
renderer so the mark scales with the widget size. -->
|
||||
<ImageView
|
||||
android:id="@+id/widget_watermark"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:scaleType="fitCenter"
|
||||
android:importantForAccessibility="no"
|
||||
android:contentDescription="@null"
|
||||
android:alpha="0.025"
|
||||
android:src="@drawable/marianum_m_watermark" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
<TextView
|
||||
android:id="@+id/widget_day_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="Heute · 08.05." />
|
||||
<TextView
|
||||
android:id="@+id/widget_day_subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
tools:text="Stand: 14:32" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="6dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_day_time_labels"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_day_grid"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_day_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
tools:text="Keine Stunden" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,185 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_background">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="-28dp"
|
||||
android:layout_marginBottom="-28dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:importantForAccessibility="no"
|
||||
android:contentDescription="@null"
|
||||
android:alpha="0.018"
|
||||
android:src="@drawable/marianum_m_watermark" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:singleLine="true"
|
||||
android:text="Heute · 06.05." />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:text="Stand: 07:50" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="6dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
||||
android:layout_marginTop="22dp">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="11sp" android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary" android:text="1." />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="07:55" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
||||
android:layout_marginTop="50dp">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="11sp" android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary" android:text="2." />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="08:40" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
||||
android:layout_marginTop="79dp">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="11sp" android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary" android:text="3." />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="09:30" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
||||
android:layout_marginTop="115dp">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="11sp" android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary" android:text="4." />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="10:35" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
||||
android:layout_marginTop="142dp">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="11sp" android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary" android:text="5." />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="11:25" />
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||
android:orientation="vertical" android:gravity="end" android:paddingEnd="3dp"
|
||||
android:layout_marginTop="169dp">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="11sp" android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary" android:text="6." />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="7sp" android:textColor="@color/widget_text_secondary" android:text="12:15" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="29dp" android:background="@color/widget_divider" />
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="56dp" android:background="@color/widget_divider" />
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="83dp" android:background="@color/widget_divider" />
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="115dp" android:background="@color/widget_divider" />
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="143dp" android:background="@color/widget_divider" />
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="170dp" android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="22dp"
|
||||
android:layout_marginTop="29dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:padding="3dp"
|
||||
android:background="@drawable/widget_lesson_block_regular">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="11sp" android:textStyle="bold"
|
||||
android:textColor="@android:color/white" android:text="MA-LK" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="55dp"
|
||||
android:layout_marginTop="83dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:padding="3dp"
|
||||
android:background="@drawable/widget_lesson_block_regular">
|
||||
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="11sp" android:textStyle="bold"
|
||||
android:textColor="@android:color/white" android:text="DE-GK" />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="9sp" android:textColor="#CCFFFFFF" android:text="B11" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="22dp"
|
||||
android:layout_marginTop="143dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:padding="3dp"
|
||||
android:background="@drawable/widget_lesson_block_cancelled">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="11sp" android:textStyle="bold"
|
||||
android:textColor="@android:color/white" android:text="BIO" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="22dp"
|
||||
android:layout_marginTop="170dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:padding="3dp"
|
||||
android:background="@drawable/widget_lesson_block_teacher_changed">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:textSize="11sp" android:textStyle="bold"
|
||||
android:textColor="@android:color/white" android:text="GE" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/widget_grid_line_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:background="@drawable/widget_grid_line" />
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/widget_lesson_block_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:paddingStart="7dp"
|
||||
android:paddingEnd="7dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:background="@drawable/widget_lesson_block_regular">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="top"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_lesson_subject"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@android:color/white"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:gravity="top|start"
|
||||
android:includeFontPadding="false"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:autoSizeMinTextSize="5sp"
|
||||
android:autoSizeMaxTextSize="11sp"
|
||||
android:autoSizeStepGranularity="1sp"
|
||||
tools:text="MA-LK" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/widget_lesson_secondary_stack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="top|end"
|
||||
android:layout_marginStart="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_lesson_room"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="12dp"
|
||||
android:textColor="#CCFFFFFF"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:gravity="end|top"
|
||||
android:includeFontPadding="false"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:autoSizeMinTextSize="5sp"
|
||||
android:autoSizeMaxTextSize="11sp"
|
||||
android:autoSizeStepGranularity="1sp"
|
||||
tools:text="A12" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_lesson_teacher"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="12dp"
|
||||
android:textColor="#B3FFFFFF"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:gravity="end|top"
|
||||
android:includeFontPadding="false"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:autoSizeMinTextSize="5sp"
|
||||
android:autoSizeMaxTextSize="11sp"
|
||||
android:autoSizeStepGranularity="1sp"
|
||||
tools:text="Müller" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_lesson_sibling_badge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|start"
|
||||
android:textStyle="bold"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:maxLines="1"
|
||||
android:includeFontPadding="false"
|
||||
android:visibility="gone"
|
||||
tools:text="+1" />
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/widget_now_indicator_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:background="@drawable/widget_now_indicator" />
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/widget_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/app_widget_background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_watermark"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:scaleType="fitCenter"
|
||||
android:importantForAccessibility="no"
|
||||
android:contentDescription="@null"
|
||||
android:alpha="0.025"
|
||||
android:src="@drawable/marianum_m_watermark" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_placeholder_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
tools:text="Marianum Vertretungsplan" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_placeholder_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
tools:text="Bitte einloggen, um den Stundenplan zu laden" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/widget_time_label_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="0dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="end"
|
||||
android:paddingEnd="3dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_time_label_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="9sp"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:singleLine="true"
|
||||
android:lineSpacingExtra="-2dp"
|
||||
tools:text="07:55" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_time_label_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="7sp"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:singleLine="true"
|
||||
android:lineSpacingExtra="-2dp"
|
||||
tools:text="1." />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,185 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/widget_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/app_widget_background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_watermark"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:scaleType="fitCenter"
|
||||
android:importantForAccessibility="no"
|
||||
android:contentDescription="@null"
|
||||
android:alpha="0.025"
|
||||
android:src="@drawable/marianum_m_watermark" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
<TextView
|
||||
android:id="@+id/widget_week_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="KW 19 · 06.05.–10.05." />
|
||||
<TextView
|
||||
android:id="@+id/widget_week_subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
tools:text="Stand: 14:32" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="6dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_week_header_mon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_week_header_tue"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_week_header_wed"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_week_header_thu"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_week_header_fri"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="2dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_week_time_labels"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_week_col_mon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_week_col_tue"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_week_col_wed"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_week_col_thu"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_divider" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_week_col_fri"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/widget_week_day_header_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="3dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_week_day_header_weekday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
tools:text="Mo" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_week_day_header_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="9sp"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
tools:text="06.05." />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Static preview for the week widget. Five day columns with two short
|
||||
demo blocks each so the structure is recognisable in the picker. -->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/widget_background">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="-36dp"
|
||||
android:layout_marginBottom="-36dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:importantForAccessibility="no"
|
||||
android:contentDescription="@null"
|
||||
android:alpha="0.018"
|
||||
android:src="@drawable/marianum_m_watermark" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:singleLine="true"
|
||||
android:text="KW 19 · 06.05.–10.05." />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:text="Stand: 07:50" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Day-name + date row -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="6dp">
|
||||
<FrameLayout android:layout_width="20dp" android:layout_height="wrap_content" />
|
||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="11sp" android:textStyle="bold" android:textColor="@color/widget_text_primary" android:text="Mo" />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textColor="@color/widget_text_secondary" android:text="06.05." />
|
||||
</LinearLayout>
|
||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="11sp" android:textStyle="bold" android:textColor="@color/widget_text_primary" android:text="Di" />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textColor="@color/widget_text_secondary" android:text="07.05." />
|
||||
</LinearLayout>
|
||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="11sp" android:textStyle="bold" android:textColor="@color/widget_text_primary" android:text="Mi" />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textColor="@color/widget_text_secondary" android:text="08.05." />
|
||||
</LinearLayout>
|
||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="11sp" android:textStyle="bold" android:textColor="@color/widget_text_primary" android:text="Do" />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textColor="@color/widget_text_secondary" android:text="09.05." />
|
||||
</LinearLayout>
|
||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="11sp" android:textStyle="bold" android:textColor="@color/widget_text_primary" android:text="Fr" />
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textColor="@color/widget_text_secondary" android:text="10.05." />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Time grid: time-label column + 5 day columns -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="2dp">
|
||||
|
||||
<FrameLayout android:layout_width="20dp" android:layout_height="match_parent">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="0dp" android:gravity="end" android:paddingEnd="3dp" android:textSize="8sp" android:textColor="@color/widget_text_secondary" android:text="08" />
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="36dp" android:gravity="end" android:paddingEnd="3dp" android:textSize="8sp" android:textColor="@color/widget_text_secondary" android:text="10" />
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="72dp" android:gravity="end" android:paddingEnd="3dp" android:textSize="8sp" android:textColor="@color/widget_text_secondary" android:text="12" />
|
||||
</FrameLayout>
|
||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||
|
||||
<!-- Mon -->
|
||||
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="2dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="MA" />
|
||||
</FrameLayout>
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="40dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="EN" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||
|
||||
<!-- Tue -->
|
||||
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="20dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_cancelled">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="BIO" />
|
||||
</FrameLayout>
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="60dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="DE" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||
|
||||
<!-- Wed -->
|
||||
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="32dp" android:layout_marginTop="2dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="MA" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||
|
||||
<!-- Thu -->
|
||||
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="2dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_teacher_changed">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="GE" />
|
||||
</FrameLayout>
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="40dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="PH" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
<FrameLayout android:layout_width="1dp" android:layout_height="match_parent" android:background="@color/widget_divider" />
|
||||
|
||||
<!-- Fri -->
|
||||
<FrameLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1">
|
||||
<FrameLayout android:layout_width="match_parent" android:layout_height="14dp" android:layout_marginTop="20dp" android:layout_marginStart="1dp" android:layout_marginEnd="1dp" android:padding="2dp" android:background="@drawable/widget_lesson_block_regular">
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="9sp" android:textStyle="bold" android:textColor="@android:color/white" android:text="EN" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#993333</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -5,6 +5,10 @@
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="widget_background">#FF1F1716</color>
|
||||
<color name="widget_text_primary">#FFF1F1F1</color>
|
||||
<color name="widget_text_secondary">#FFB0B0B0</color>
|
||||
<color name="widget_divider">#33FFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#993333</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="widget_day_label">Marianum · Heute</string>
|
||||
<string name="widget_week_label">Marianum · Woche</string>
|
||||
<string name="widget_day_description">Stundenplan und Vertretungen für den anstehenden Schultag.</string>
|
||||
<string name="widget_week_description">Stundenplan und Vertretungen für die ganze Schulwoche.</string>
|
||||
<string name="widget_no_lessons">Keine Stunden</string>
|
||||
<string name="widget_holiday">Ferien</string>
|
||||
<string name="widget_login_required">Bitte einloggen, um den Stundenplan zu laden</string>
|
||||
<string name="widget_loading">Lade…</string>
|
||||
<string name="widget_status_label">Stand: %1$s</string>
|
||||
<string name="widget_today">Heute</string>
|
||||
<string name="widget_tomorrow">Morgen</string>
|
||||
<string name="widget_placeholder_title">Marianum Stundenplan</string>
|
||||
<string name="widget_calendar_week_prefix">KW</string>
|
||||
<string name="widget_yesterday_prefix">gestern</string>
|
||||
</resources>
|
||||
@@ -7,6 +7,7 @@
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Status colors mirror lib/view/pages/timetable/data/lesson_color.dart
|
||||
exactly so widget tiles match the in-app calendar. -->
|
||||
<color name="widget_lesson_regular">#FF993333</color>
|
||||
<color name="widget_lesson_ongoing">#FFC83333</color>
|
||||
<color name="widget_lesson_past">#FF993333</color>
|
||||
<color name="widget_lesson_cancelled">#FF000000</color>
|
||||
<color name="widget_lesson_irregular">#FF8F19B3</color>
|
||||
<color name="widget_lesson_teacher_changed">#FF29639B</color>
|
||||
<color name="widget_lesson_event">#FF2E7D32</color>
|
||||
|
||||
<color name="widget_background">#FFFCF7F5</color>
|
||||
<color name="widget_text_primary">#FF111111</color>
|
||||
<color name="widget_text_secondary">#FF555555</color>
|
||||
<color name="widget_divider">#22000000</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Auto-Backup rules for Android 11 and below.
|
||||
Excludes the home_widget plugin's SharedPreferences file
|
||||
(HomeWidgetPreferences) so the cached timetable — which contains
|
||||
teacher names, room numbers and personal custom events — is not
|
||||
uploaded to the user's Google Drive. -->
|
||||
<full-backup-content>
|
||||
<exclude domain="sharedpref" path="HomeWidgetPreferences.xml"/>
|
||||
</full-backup-content>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Backup + device-transfer rules for Android 12+.
|
||||
Excludes the home_widget plugin's SharedPreferences file
|
||||
(HomeWidgetPreferences) so the cached timetable — which contains
|
||||
teacher names, room numbers and personal custom events — is not
|
||||
uploaded to the user's Google Drive or transferred to a new device.
|
||||
The widget repopulates from a fresh Webuntis fetch after sign-in. -->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<exclude domain="sharedpref" path="HomeWidgetPreferences.xml"/>
|
||||
</cloud-backup>
|
||||
<device-transfer>
|
||||
<exclude domain="sharedpref" path="HomeWidgetPreferences.xml"/>
|
||||
</device-transfer>
|
||||
</data-extraction-rules>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:initialLayout="@layout/widget_placeholder"
|
||||
android:minWidth="110dp"
|
||||
android:minHeight="180dp"
|
||||
android:targetCellWidth="2"
|
||||
android:targetCellHeight="5"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:widgetCategory="home_screen"
|
||||
android:updatePeriodMillis="0"
|
||||
android:description="@string/widget_day_description"
|
||||
android:previewLayout="@layout/widget_day_preview" />
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:initialLayout="@layout/widget_placeholder"
|
||||
android:minWidth="320dp"
|
||||
android:minHeight="240dp"
|
||||
android:targetCellWidth="5"
|
||||
android:targetCellHeight="5"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:widgetCategory="home_screen"
|
||||
android:updatePeriodMillis="0"
|
||||
android:description="@string/widget_week_description"
|
||||
android:previewLayout="@layout/widget_week_preview" />
|
||||
@@ -1,5 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="eu.mhsl.marianum.mobile.client">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.6.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
@@ -22,6 +9,29 @@ rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
// Pin every Android subproject to JVM 17 so plugins that ship Kotlin sources
|
||||
// compiled with a higher target (e.g. receive_sharing_intent at 21) or stale
|
||||
// Java compatibility (e.g. home_widget at 1.8) don't break the build under
|
||||
// newer Gradle/Kotlin tooling. Registered before evaluationDependsOn so the
|
||||
// afterEvaluate fires at the right point in the lifecycle.
|
||||
subprojects { sub ->
|
||||
sub.afterEvaluate {
|
||||
if (sub.hasProperty('android')) {
|
||||
sub.android {
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
}
|
||||
sub.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/gen" />
|
||||
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/gen" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/app/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/app/src/main/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/app/src/main/assets" />
|
||||
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/app/src/main/libs" />
|
||||
<option name="PROGUARD_LOGS_FOLDER_RELATIVE_PATH" value="/app/src/main/proguard_logs" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/app/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 29 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Flutter for Android" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,3 +1,6 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.jvmargs=-Xmx4G
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainVersion=21
|
||||
@@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
include ':app'
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}
|
||||
settings.ext.flutterSdkPath = flutterSdkPath()
|
||||
|
||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||
def properties = new Properties()
|
||||
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
||||
|
||||
assert localPropertiesFile.exists()
|
||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version '8.13.2' apply false
|
||||
id "com.android.library" version '8.13.2' apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.10" apply false
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.10.0'
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
||||