Compare commits
78 Commits
a1fd21de04
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| d8a5ccfe80 | |||
| 1ae3f7bb83 | |||
| 8c76f2d816 | |||
| c46f14f6a6 | |||
| b2b00d321e | |||
| 1a11b9ac60 | |||
| a0bc46f522 | |||
| 1458d8ce49 | |||
| 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 | |||
| bfa0b0f5c0 | |||
| 274b77f705 | |||
| b68bec9ebd | |||
| 81f65750b7 |
@@ -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,44 +1,88 @@
|
|||||||
# This file configures the analyzer, which statically analyzes Dart code to
|
# Static analysis configuration for the Flutter project.
|
||||||
# check for errors, warnings, and lints.
|
# https://dart.dev/guides/language/analysis-options
|
||||||
#
|
#
|
||||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
# Base ruleset: flutter_lints (recommended Flutter defaults).
|
||||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
# Additional lints below catch real bugs and enforce consistent style.
|
||||||
# invoked from the command line by running `flutter analyze`.
|
|
||||||
|
|
||||||
# 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
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
analyzer:
|
analyzer:
|
||||||
|
language:
|
||||||
|
strict-casts: true
|
||||||
|
strict-raw-types: true
|
||||||
errors:
|
errors:
|
||||||
invalid_annotation_target: ignore
|
invalid_annotation_target: ignore
|
||||||
|
todo: ignore
|
||||||
|
exclude:
|
||||||
|
- "**/*.g.dart"
|
||||||
|
- "**/*.freezed.dart"
|
||||||
|
- "lib/firebase_options.dart"
|
||||||
|
- "build/**"
|
||||||
|
|
||||||
linter:
|
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:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# === Project conventions ===
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
|
||||||
file_names: false
|
|
||||||
prefer_relative_imports: true
|
prefer_relative_imports: true
|
||||||
unnecessary_lambdas: true
|
|
||||||
prefer_single_quotes: true
|
prefer_single_quotes: true
|
||||||
prefer_if_elements_to_conditional_expressions: true
|
|
||||||
prefer_expression_function_bodies: true
|
|
||||||
omit_local_variable_types: true
|
|
||||||
eol_at_end_of_file: true
|
eol_at_end_of_file: true
|
||||||
cast_nullable_to_non_nullable: true
|
omit_local_variable_types: true
|
||||||
avoid_void_async: true
|
|
||||||
avoid_multiple_declarations_per_line: true
|
avoid_multiple_declarations_per_line: true
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# === Bug catchers ===
|
||||||
# https://dart.dev/guides/language/analysis-options
|
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
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ if (flutterVersionName == null) {
|
|||||||
android {
|
android {
|
||||||
namespace "eu.mhsl.marianum.mobile.client"
|
namespace "eu.mhsl.marianum.mobile.client"
|
||||||
compileSdk flutter.compileSdkVersion
|
compileSdk flutter.compileSdkVersion
|
||||||
ndkVersion "27.0.12077973"
|
ndkVersion "28.2.13676358"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_17
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
<application
|
<application
|
||||||
android:label="Marianum Fulda"
|
android:label="Marianum Fulda"
|
||||||
android:name="${applicationName}"
|
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
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -23,23 +25,85 @@
|
|||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<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>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
</application>
|
|
||||||
<!-- Required to query activities that can process text, see:
|
|
||||||
https://developer.android.com/training/package-visibility?hl=en and
|
|
||||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
|
||||||
|
|
||||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
<!-- 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>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain"/>
|
||||||
</intent>
|
</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>
|
</queries>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<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>
|
</manifest>
|
||||||
|
|||||||
@@ -1,5 +1,42 @@
|
|||||||
package eu.mhsl.marianum.mobile.client
|
package eu.mhsl.marianum.mobile.client
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
class MainActivity: FlutterActivity()
|
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,740 @@
|
|||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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"
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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,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,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,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,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>
|
||||||
@@ -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" />
|
||||||
@@ -9,6 +9,29 @@ rootProject.buildDir = '../build'
|
|||||||
subprojects {
|
subprojects {
|
||||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
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 {
|
subprojects {
|
||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#This file is generated by updateDaemonJvm
|
||||||
|
toolchainVersion=21
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "8.9.1" apply false
|
id "com.android.application" version '8.13.2' apply false
|
||||||
id "com.android.library" version "8.9.1" 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.jetbrains.kotlin.android" version "2.2.20" apply false
|
||||||
|
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.10.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="70.000168mm"
|
||||||
|
height="82.227348mm"
|
||||||
|
viewBox="0 0 70.000168 82.227348"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||||
|
id="defs1" /><g
|
||||||
|
id="g1"
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458334,107.44411,-80.482198)"><g
|
||||||
|
id="group-R5"
|
||||||
|
transform="translate(-749.41293,290.52252)"><path
|
||||||
|
id="path3"
|
||||||
|
d="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"
|
||||||
|
style="fill:#d3d2d2;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||||
|
transform="matrix(0.13333333,0,0,-0.13333333,0,632.14667)" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -1 +1,4 @@
|
|||||||
extensions:
|
extensions:
|
||||||
|
- hive_ce: true
|
||||||
|
- shared_preferences: true
|
||||||
|
- provider: true
|
||||||
@@ -54,7 +54,7 @@ flutter_native_splash:
|
|||||||
# 640 pixels in diameter.
|
# 640 pixels in diameter.
|
||||||
# App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
|
# App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
|
||||||
# 768 pixels in diameter.
|
# 768 pixels in diameter.
|
||||||
image: assets/logo/icon-android12.png
|
image: assets/logo/icon/icon-android12.png
|
||||||
|
|
||||||
# Splash screen background color.
|
# Splash screen background color.
|
||||||
color: "#993333"
|
color: "#993333"
|
||||||
|
|||||||
@@ -20,7 +20,5 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>13.0</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "../Pods/Target Support Files/Pods-Share Extension/Pods-Share Extension.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "../Pods/Target Support Files/Pods-Share Extension/Pods-Share Extension.profile.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "../Pods/Target Support Files/Pods-Share Extension/Pods-Share Extension.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#include "Generated.xcconfig"
|
||||||
@@ -34,6 +34,10 @@ target 'Runner' do
|
|||||||
pod 'PhoneNumberKit', '~> 3.7.6'
|
pod 'PhoneNumberKit', '~> 3.7.6'
|
||||||
|
|
||||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
|
||||||
|
target 'Share Extension' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
# target 'RunnerTests' do
|
# target 'RunnerTests' do
|
||||||
# inherit! :search_paths
|
# inherit! :search_paths
|
||||||
# end
|
# end
|
||||||
|
|||||||
@@ -36,53 +36,37 @@ PODS:
|
|||||||
- SwiftyGif
|
- SwiftyGif
|
||||||
- emoji_picker_flutter (0.0.1):
|
- emoji_picker_flutter (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- fast_rsa (0.7.0):
|
- eraser (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
- DKImagePickerController/PhotoGallery
|
- DKImagePickerController/PhotoGallery
|
||||||
- Flutter
|
- Flutter
|
||||||
- Firebase/CoreOnly (12.4.0):
|
- Firebase/CoreOnly (12.12.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.12.0)
|
||||||
- Firebase/InAppMessaging (12.4.0):
|
- Firebase/Messaging (12.12.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseInAppMessaging (~> 12.4.0-beta)
|
- FirebaseMessaging (~> 12.12.0)
|
||||||
- Firebase/Messaging (12.4.0):
|
- firebase_core (4.7.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly (= 12.12.0)
|
||||||
- FirebaseMessaging (~> 12.4.0)
|
|
||||||
- firebase_core (4.2.1):
|
|
||||||
- Firebase/CoreOnly (= 12.4.0)
|
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_in_app_messaging (0.9.0-4):
|
- firebase_messaging (16.2.0):
|
||||||
- Firebase/InAppMessaging (= 12.4.0)
|
- Firebase/Messaging (= 12.12.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (16.0.4):
|
- FirebaseCore (12.12.1):
|
||||||
- Firebase/Messaging (= 12.4.0)
|
- FirebaseCoreInternal (~> 12.12.0)
|
||||||
- firebase_core
|
|
||||||
- Flutter
|
|
||||||
- FirebaseABTesting (12.4.0):
|
|
||||||
- FirebaseCore (~> 12.4.0)
|
|
||||||
- FirebaseCore (12.4.0):
|
|
||||||
- FirebaseCoreInternal (~> 12.4.0)
|
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Logger (~> 8.1)
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
- FirebaseCoreInternal (12.4.0):
|
- FirebaseCoreInternal (12.12.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- FirebaseInAppMessaging (12.4.0-beta):
|
- FirebaseInstallations (12.12.0):
|
||||||
- FirebaseABTesting (~> 12.4.0)
|
- FirebaseCore (~> 12.12.0)
|
||||||
- FirebaseCore (~> 12.4.0)
|
|
||||||
- FirebaseInstallations (~> 12.4.0)
|
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
|
||||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
|
||||||
- nanopb (~> 3.30910.0)
|
|
||||||
- FirebaseInstallations (12.4.0):
|
|
||||||
- FirebaseCore (~> 12.4.0)
|
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseMessaging (12.4.0):
|
- FirebaseMessaging (12.12.0):
|
||||||
- FirebaseCore (~> 12.4.0)
|
- FirebaseCore (~> 12.12.0)
|
||||||
- FirebaseInstallations (~> 12.4.0)
|
- FirebaseInstallations (~> 12.12.0)
|
||||||
- GoogleDataTransport (~> 10.1)
|
- GoogleDataTransport (~> 10.1)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
@@ -96,6 +80,9 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- flutter_native_splash (2.4.3):
|
- flutter_native_splash (2.4.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- flutter_secure_storage_darwin (10.0.0):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- GoogleDataTransport (10.1.0):
|
- GoogleDataTransport (10.1.0):
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
@@ -123,6 +110,8 @@ PODS:
|
|||||||
- GoogleUtilities/UserDefaults (8.1.0):
|
- GoogleUtilities/UserDefaults (8.1.0):
|
||||||
- GoogleUtilities/Logger
|
- GoogleUtilities/Logger
|
||||||
- GoogleUtilities/Privacy
|
- GoogleUtilities/Privacy
|
||||||
|
- home_widget (0.0.1):
|
||||||
|
- Flutter
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- in_app_review (2.0.0):
|
- in_app_review (2.0.0):
|
||||||
@@ -136,9 +125,6 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_foundation (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- PhoneNumberKit (3.7.11):
|
- PhoneNumberKit (3.7.11):
|
||||||
- PhoneNumberKit/PhoneNumberKitCore (= 3.7.11)
|
- PhoneNumberKit/PhoneNumberKitCore (= 3.7.11)
|
||||||
- PhoneNumberKit/UIKit (= 3.7.11)
|
- PhoneNumberKit/UIKit (= 3.7.11)
|
||||||
@@ -146,9 +132,13 @@ PODS:
|
|||||||
- PhoneNumberKit/UIKit (3.7.11):
|
- PhoneNumberKit/UIKit (3.7.11):
|
||||||
- PhoneNumberKit/PhoneNumberKitCore
|
- PhoneNumberKit/PhoneNumberKitCore
|
||||||
- PromisesObjC (2.4.0)
|
- PromisesObjC (2.4.0)
|
||||||
- SDWebImage (5.21.2):
|
- receive_sharing_intent (1.8.1):
|
||||||
- SDWebImage/Core (= 5.21.2)
|
- Flutter
|
||||||
- SDWebImage/Core (5.21.2)
|
- screen_brightness_ios (2.1.3):
|
||||||
|
- Flutter
|
||||||
|
- SDWebImage (5.21.7):
|
||||||
|
- SDWebImage/Core (= 5.21.7)
|
||||||
|
- SDWebImage/Core (5.21.7)
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
@@ -162,41 +152,51 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- video_player_avfoundation (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- wakelock_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- workmanager_apple (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`)
|
- emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`)
|
||||||
- fast_rsa (from `.symlinks/plugins/fast_rsa/ios`)
|
- eraser (from `.symlinks/plugins/eraser/ios`)
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
- firebase_in_app_messaging (from `.symlinks/plugins/firebase_in_app_messaging/ios`)
|
|
||||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_app_badge (from `.symlinks/plugins/flutter_app_badge/ios`)
|
- flutter_app_badge (from `.symlinks/plugins/flutter_app_badge/ios`)
|
||||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
|
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
|
||||||
|
- home_widget (from `.symlinks/plugins/home_widget/ios`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
||||||
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
|
||||||
- PhoneNumberKit (~> 3.7.6)
|
- PhoneNumberKit (~> 3.7.6)
|
||||||
|
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||||
|
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- syncfusion_flutter_pdfviewer (from `.symlinks/plugins/syncfusion_flutter_pdfviewer/ios`)
|
- syncfusion_flutter_pdfviewer (from `.symlinks/plugins/syncfusion_flutter_pdfviewer/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||||
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
|
- workmanager_apple (from `.symlinks/plugins/workmanager_apple/ios`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- DKImagePickerController
|
- DKImagePickerController
|
||||||
- DKPhotoGallery
|
- DKPhotoGallery
|
||||||
- Firebase
|
- Firebase
|
||||||
- FirebaseABTesting
|
|
||||||
- FirebaseCore
|
- FirebaseCore
|
||||||
- FirebaseCoreInternal
|
- FirebaseCoreInternal
|
||||||
- FirebaseInAppMessaging
|
|
||||||
- FirebaseInstallations
|
- FirebaseInstallations
|
||||||
- FirebaseMessaging
|
- FirebaseMessaging
|
||||||
- GoogleDataTransport
|
- GoogleDataTransport
|
||||||
@@ -214,14 +214,12 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
emoji_picker_flutter:
|
emoji_picker_flutter:
|
||||||
:path: ".symlinks/plugins/emoji_picker_flutter/ios"
|
:path: ".symlinks/plugins/emoji_picker_flutter/ios"
|
||||||
fast_rsa:
|
eraser:
|
||||||
:path: ".symlinks/plugins/fast_rsa/ios"
|
:path: ".symlinks/plugins/eraser/ios"
|
||||||
file_picker:
|
file_picker:
|
||||||
:path: ".symlinks/plugins/file_picker/ios"
|
:path: ".symlinks/plugins/file_picker/ios"
|
||||||
firebase_core:
|
firebase_core:
|
||||||
:path: ".symlinks/plugins/firebase_core/ios"
|
:path: ".symlinks/plugins/firebase_core/ios"
|
||||||
firebase_in_app_messaging:
|
|
||||||
:path: ".symlinks/plugins/firebase_in_app_messaging/ios"
|
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
:path: ".symlinks/plugins/firebase_messaging/ios"
|
:path: ".symlinks/plugins/firebase_messaging/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
@@ -232,6 +230,10 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
|
flutter_secure_storage_darwin:
|
||||||
|
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
|
||||||
|
home_widget:
|
||||||
|
:path: ".symlinks/plugins/home_widget/ios"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
in_app_review:
|
in_app_review:
|
||||||
@@ -240,8 +242,10 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/open_filex/ios"
|
:path: ".symlinks/plugins/open_filex/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_foundation:
|
receive_sharing_intent:
|
||||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
||||||
|
screen_brightness_ios:
|
||||||
|
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
@@ -252,6 +256,12 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/syncfusion_flutter_pdfviewer/ios"
|
:path: ".symlinks/plugins/syncfusion_flutter_pdfviewer/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
video_player_avfoundation:
|
||||||
|
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
||||||
|
wakelock_plus:
|
||||||
|
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||||
|
workmanager_apple:
|
||||||
|
:path: ".symlinks/plugins/workmanager_apple/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
@@ -259,40 +269,43 @@ SPEC CHECKSUMS:
|
|||||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
|
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
|
||||||
fast_rsa: fb70897d51040b094c780d5f1d7358614738b879
|
eraser: 83a4b06985f3702aa3d8dec816f9693266012937
|
||||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e
|
Firebase: aa154fee4e9b8eac17aa42344988865b3e857d33
|
||||||
firebase_core: f1aafb21c14f497e5498f7ffc4dc63cbb52b2594
|
firebase_core: 9156a152117c843440b0b990c785aa0259bc5447
|
||||||
firebase_in_app_messaging: 04dfc07ab81578ef83bf0c0229be258ddf287c4f
|
firebase_messaging: 0d962ab44ff24ed36deb8fa2ee043c4671858269
|
||||||
firebase_messaging: c17a29984eafce4b2997fe078bb0a9e0b06f5dde
|
FirebaseCore: 86241206e656f5c80c995e370e6c975913b9b284
|
||||||
FirebaseABTesting: c05b5ec9f1d9f21a65909525de301d375032d9a4
|
FirebaseCoreInternal: 7c12fc3011d889085e765e317d7b9fd1cef97af9
|
||||||
FirebaseCore: bb595f3114953664e3c1dc032f008a244147cfd3
|
FirebaseInstallations: 4e6e162aa4abaaeeeb01dd00179dfc5ad9c2194e
|
||||||
FirebaseCoreInternal: d7f5a043c2cd01a08103ab586587c1468047bca6
|
FirebaseMessaging: 341004946fa7ffc741344b20f1b667514fc93e31
|
||||||
FirebaseInAppMessaging: 606dd4d4d5590a3d8229f363fdebb485235985b2
|
|
||||||
FirebaseInstallations: ae9f4902cb5bf1d0c5eaa31ec1f4e5495a0714e2
|
|
||||||
FirebaseMessaging: d33971b7bb252745ea6cd31ab190d1a1df4b8ed5
|
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
flutter_app_badge: ca742dd659a157c1090ef7cd881cb78f48f3bcdf
|
flutter_app_badge: ca742dd659a157c1090ef7cd881cb78f48f3bcdf
|
||||||
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
|
flutter_local_notifications: 643a3eda1ce1c0599413ca31672536d423dee214
|
||||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||||
|
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
|
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
|
||||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||||
in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937
|
in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1
|
open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
|
||||||
PhoneNumberKit: ced55861269312a5e3bc2ef82a58d6255b1c976a
|
PhoneNumberKit: ced55861269312a5e3bc2ef82a58d6255b1c976a
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
SDWebImage: 9f177d83116802728e122410fb25ad88f5c7608a
|
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
|
||||||
|
screen_brightness_ios: 212d950bb99c915eee971c884f4a6c87c92cd13d
|
||||||
|
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
syncfusion_flutter_pdfviewer: 90dc48305d2e33d4aa20681d1e98ddeda891bc14
|
syncfusion_flutter_pdfviewer: 90dc48305d2e33d4aa20681d1e98ddeda891bc14
|
||||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
|
video_player_avfoundation: 3453f792138786248960ca029747fcd9f318ef52
|
||||||
|
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||||
|
workmanager_apple: 904529ae31e97fc5be632cf628507652294a0778
|
||||||
|
|
||||||
PODFILE CHECKSUM: e21c9d4c7b9623c73c6784ddc132fd50a603ad93
|
PODFILE CHECKSUM: 424a9b4c0fe81d8ebeaa9cb0dfedb60a68b19a0d
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -7,17 +7,50 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
034BD2FF7A860C6DC2FED514 /* Pods_Share_Extension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F2428AC5384E0EF8DAB462A /* Pods_Share_Extension.framework */; };
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
3321F80F2FB1C00C0011C712 /* Share Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3321F8052FB1C00C0011C712 /* Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
33FDB0982EE9ABDC000B2391 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 33FDB0972EE9ABDC000B2391 /* GoogleService-Info.plist */; };
|
33FDB0982EE9ABDC000B2391 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 33FDB0972EE9ABDC000B2391 /* GoogleService-Info.plist */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
AA0101070000000011111111 /* TimetableWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = AA0101020000000011111111 /* TimetableWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
AA0102010000000022222222 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0102020000000022222222 /* SceneDelegate.swift */; };
|
||||||
B8263932DB64B022CCEE7A53 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 90960A132A5F91779B3FBE28 /* Pods_Runner.framework */; };
|
B8263932DB64B022CCEE7A53 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 90960A132A5F91779B3FBE28 /* Pods_Runner.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
3321F80D2FB1C00C0011C712 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 3321F8042FB1C00C0011C712;
|
||||||
|
remoteInfo = "Share Extension";
|
||||||
|
};
|
||||||
|
AA0101080000000011111111 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = AA0101010000000011111111;
|
||||||
|
remoteInfo = TimetableWidgetExtension;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
3321F8102FB1C00C0011C712 /* Embed Foundation Extensions */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 13;
|
||||||
|
files = (
|
||||||
|
3321F80F2FB1C00C0011C712 /* Share Extension.appex in Embed Foundation Extensions */,
|
||||||
|
AA0101070000000011111111 /* TimetableWidgetExtension.appex in Embed Foundation Extensions */,
|
||||||
|
);
|
||||||
|
name = "Embed Foundation Extensions";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
isa = PBXCopyFilesBuildPhase;
|
isa = PBXCopyFilesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -33,9 +66,12 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
3321F8052FB1C00C0011C712 /* Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
33FDB0972EE9ABDC000B2391 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
|
33FDB0972EE9ABDC000B2391 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
4509EC31CB08BA9BF367AF6C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
4509EC31CB08BA9BF367AF6C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
4F2428AC5384E0EF8DAB462A /* Pods_Share_Extension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Share_Extension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
60E1803A3FB28FCC6F435E99 /* Pods-Share Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.release.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
64801C012A9112D500E8B558 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
64801C012A9112D500E8B558 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
@@ -48,11 +84,73 @@
|
|||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
AA0101020000000011111111 /* TimetableWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TimetableWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
AA0102020000000022222222 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||||
AA6B03D1433E7395021F7730 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
AA6B03D1433E7395021F7730 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
BB0001010000000011111111 /* ShareExtension-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "ShareExtension-Debug.xcconfig"; path = "Flutter/ShareExtension-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
BB0001020000000011111111 /* ShareExtension-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "ShareExtension-Release.xcconfig"; path = "Flutter/ShareExtension-Release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
BB0001030000000011111111 /* ShareExtension-Profile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "ShareExtension-Profile.xcconfig"; path = "Flutter/ShareExtension-Profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
BB0001040000000011111111 /* TimetableWidget-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "TimetableWidget-Debug.xcconfig"; path = "Flutter/TimetableWidget-Debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
BB0001050000000011111111 /* TimetableWidget-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "TimetableWidget-Release.xcconfig"; path = "Flutter/TimetableWidget-Release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
BB0001060000000011111111 /* TimetableWidget-Profile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "TimetableWidget-Profile.xcconfig"; path = "Flutter/TimetableWidget-Profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
C7E1879BE78835C7E3256316 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
C7E1879BE78835C7E3256316 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
DD904D7C0FC0AD11449CEB80 /* Pods-Share Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.debug.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
EF5279D9BF8FCBB117AF998E /* Pods-Share Extension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.profile.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
AA01010E0000000011111111 /* Exceptions for "Share Extension" folder in "Share Extension" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
);
|
||||||
|
target = 3321F8042FB1C00C0011C712 /* Share Extension */;
|
||||||
|
};
|
||||||
|
AA01010F0000000011111111 /* Exceptions for "TimetableWidgetExtension" folder in "TimetableWidgetExtension" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
);
|
||||||
|
target = AA0101010000000011111111 /* TimetableWidgetExtension */;
|
||||||
|
};
|
||||||
|
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
|
||||||
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
3321F8062FB1C00C0011C712 /* Share Extension */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
AA01010E0000000011111111 /* Exceptions for "Share Extension" folder in "Share Extension" target */,
|
||||||
|
);
|
||||||
|
explicitFileTypes = {
|
||||||
|
};
|
||||||
|
explicitFolders = (
|
||||||
|
);
|
||||||
|
path = "Share Extension";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
AA0101030000000011111111 /* TimetableWidgetExtension */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
AA01010F0000000011111111 /* Exceptions for "TimetableWidgetExtension" folder in "TimetableWidgetExtension" target */,
|
||||||
|
);
|
||||||
|
explicitFileTypes = {
|
||||||
|
};
|
||||||
|
explicitFolders = (
|
||||||
|
);
|
||||||
|
path = TimetableWidgetExtension;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
3321F8022FB1C00C0011C712 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
034BD2FF7A860C6DC2FED514 /* Pods_Share_Extension.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -61,6 +159,13 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
AA0101050000000011111111 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
@@ -70,6 +175,9 @@
|
|||||||
C7E1879BE78835C7E3256316 /* Pods-Runner.debug.xcconfig */,
|
C7E1879BE78835C7E3256316 /* Pods-Runner.debug.xcconfig */,
|
||||||
AA6B03D1433E7395021F7730 /* Pods-Runner.release.xcconfig */,
|
AA6B03D1433E7395021F7730 /* Pods-Runner.release.xcconfig */,
|
||||||
4509EC31CB08BA9BF367AF6C /* Pods-Runner.profile.xcconfig */,
|
4509EC31CB08BA9BF367AF6C /* Pods-Runner.profile.xcconfig */,
|
||||||
|
DD904D7C0FC0AD11449CEB80 /* Pods-Share Extension.debug.xcconfig */,
|
||||||
|
60E1803A3FB28FCC6F435E99 /* Pods-Share Extension.release.xcconfig */,
|
||||||
|
EF5279D9BF8FCBB117AF998E /* Pods-Share Extension.profile.xcconfig */,
|
||||||
);
|
);
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -78,6 +186,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
90960A132A5F91779B3FBE28 /* Pods_Runner.framework */,
|
90960A132A5F91779B3FBE28 /* Pods_Runner.framework */,
|
||||||
|
4F2428AC5384E0EF8DAB462A /* Pods_Share_Extension.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -89,6 +198,12 @@
|
|||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
BB0001010000000011111111 /* ShareExtension-Debug.xcconfig */,
|
||||||
|
BB0001020000000011111111 /* ShareExtension-Release.xcconfig */,
|
||||||
|
BB0001030000000011111111 /* ShareExtension-Profile.xcconfig */,
|
||||||
|
BB0001040000000011111111 /* TimetableWidget-Debug.xcconfig */,
|
||||||
|
BB0001050000000011111111 /* TimetableWidget-Release.xcconfig */,
|
||||||
|
BB0001060000000011111111 /* TimetableWidget-Profile.xcconfig */,
|
||||||
);
|
);
|
||||||
name = Flutter;
|
name = Flutter;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -98,6 +213,8 @@
|
|||||||
children = (
|
children = (
|
||||||
9740EEB11CF90186004384FC /* Flutter */,
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
3321F8062FB1C00C0011C712 /* Share Extension */,
|
||||||
|
AA0101030000000011111111 /* TimetableWidgetExtension */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
345F4BD4143471FDA71626DE /* Pods */,
|
345F4BD4143471FDA71626DE /* Pods */,
|
||||||
731388A08E3B330B216381D0 /* Frameworks */,
|
731388A08E3B330B216381D0 /* Frameworks */,
|
||||||
@@ -108,6 +225,8 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
3321F8052FB1C00C0011C712 /* Share Extension.appex */,
|
||||||
|
AA0101020000000011111111 /* TimetableWidgetExtension.appex */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -124,6 +243,7 @@
|
|||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
AA0102020000000022222222 /* SceneDelegate.swift */,
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
);
|
);
|
||||||
path = Runner;
|
path = Runner;
|
||||||
@@ -132,6 +252,27 @@
|
|||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
3321F8042FB1C00C0011C712 /* Share Extension */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 3321F8152FB1C00C0011C712 /* Build configuration list for PBXNativeTarget "Share Extension" */;
|
||||||
|
buildPhases = (
|
||||||
|
AC0316D13BD5FB74CD9B5223 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
3321F8012FB1C00C0011C712 /* Sources */,
|
||||||
|
3321F8022FB1C00C0011C712 /* Frameworks */,
|
||||||
|
3321F8032FB1C00C0011C712 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
3321F8062FB1C00C0011C712 /* Share Extension */,
|
||||||
|
);
|
||||||
|
name = "Share Extension";
|
||||||
|
productName = "Share Extension";
|
||||||
|
productReference = 3321F8052FB1C00C0011C712 /* Share Extension.appex */;
|
||||||
|
productType = "com.apple.product-type.app-extension";
|
||||||
|
};
|
||||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
@@ -142,6 +283,7 @@
|
|||||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3321F8102FB1C00C0011C712 /* Embed Foundation Extensions */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
174B54D80220E5F588BD9737 /* [CP] Embed Pods Frameworks */,
|
174B54D80220E5F588BD9737 /* [CP] Embed Pods Frameworks */,
|
||||||
859FAB4E05FAC31B7B1A62D7 /* [CP] Copy Pods Resources */,
|
859FAB4E05FAC31B7B1A62D7 /* [CP] Copy Pods Resources */,
|
||||||
@@ -149,12 +291,34 @@
|
|||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
3321F80E2FB1C00C0011C712 /* PBXTargetDependency */,
|
||||||
|
AA0101090000000011111111 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = Runner;
|
name = Runner;
|
||||||
productName = Runner;
|
productName = Runner;
|
||||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
|
AA0101010000000011111111 /* TimetableWidgetExtension */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = AA01010D0000000011111111 /* Build configuration list for PBXNativeTarget "TimetableWidgetExtension" */;
|
||||||
|
buildPhases = (
|
||||||
|
AA0101040000000011111111 /* Sources */,
|
||||||
|
AA0101050000000011111111 /* Frameworks */,
|
||||||
|
AA0101060000000011111111 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
AA0101030000000011111111 /* TimetableWidgetExtension */,
|
||||||
|
);
|
||||||
|
name = TimetableWidgetExtension;
|
||||||
|
productName = TimetableWidgetExtension;
|
||||||
|
productReference = AA0101020000000011111111 /* TimetableWidgetExtension.appex */;
|
||||||
|
productType = "com.apple.product-type.app-extension";
|
||||||
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
/* Begin PBXProject section */
|
||||||
@@ -162,13 +326,20 @@
|
|||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastSwiftUpdateCheck = 2610;
|
||||||
LastUpgradeCheck = 1510;
|
LastUpgradeCheck = 1510;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
3321F8042FB1C00C0011C712 = {
|
||||||
|
CreatedOnToolsVersion = 26.1.1;
|
||||||
|
};
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
CreatedOnToolsVersion = 7.3.1;
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
LastSwiftMigration = 1100;
|
LastSwiftMigration = 1100;
|
||||||
};
|
};
|
||||||
|
AA0101010000000011111111 = {
|
||||||
|
CreatedOnToolsVersion = 26.1.1;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
@@ -185,11 +356,20 @@
|
|||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
97C146ED1CF9000F007C117D /* Runner */,
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
3321F8042FB1C00C0011C712 /* Share Extension */,
|
||||||
|
AA0101010000000011111111 /* TimetableWidgetExtension */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
3321F8032FB1C00C0011C712 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -202,6 +382,13 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
AA0101060000000011111111 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
@@ -213,14 +400,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
@@ -250,14 +433,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Copy Pods Resources";
|
name = "[CP] Copy Pods Resources";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
@@ -278,6 +457,28 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
};
|
};
|
||||||
|
AC0316D13BD5FB74CD9B5223 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Share Extension-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
EE78ADC5E762D17A29097E92 /* [CP] Check Pods Manifest.lock */ = {
|
EE78ADC5E762D17A29097E92 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -303,17 +504,45 @@
|
|||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
3321F8012FB1C00C0011C712 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
AA0102010000000022222222 /* SceneDelegate.swift in Sources */,
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
AA0101040000000011111111 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
3321F80E2FB1C00C0011C712 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 3321F8042FB1C00C0011C712 /* Share Extension */;
|
||||||
|
targetProxy = 3321F80D2FB1C00C0011C712 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
AA0101090000000011111111 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = AA0101010000000011111111 /* TimetableWidgetExtension */;
|
||||||
|
targetProxy = AA0101080000000011111111 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
@@ -375,7 +604,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -394,6 +623,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.eu.mhsl.marianum.mobile.client.share;
|
||||||
DEVELOPMENT_TEAM = MY55VF3KPG;
|
DEVELOPMENT_TEAM = MY55VF3KPG;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -403,7 +633,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "${FLUTTER_BUILD_NAME)";
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client;
|
PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@@ -413,6 +643,132 @@
|
|||||||
};
|
};
|
||||||
name = Profile;
|
name = Profile;
|
||||||
};
|
};
|
||||||
|
3321F8112FB1C00C0011C712 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = BB0001010000000011111111 /* ShareExtension-Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements";
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.eu.mhsl.marianum.mobile.client.share;
|
||||||
|
DEVELOPMENT_TEAM = MY55VF3KPG;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
INFOPLIST_FILE = "Share Extension/Info.plist";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "eu.mhsl.marianum.mobile.client.Share-Extension";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
3321F8122FB1C00C0011C712 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = BB0001020000000011111111 /* ShareExtension-Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements";
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.eu.mhsl.marianum.mobile.client.share;
|
||||||
|
DEVELOPMENT_TEAM = MY55VF3KPG;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
INFOPLIST_FILE = "Share Extension/Info.plist";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "eu.mhsl.marianum.mobile.client.Share-Extension";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
3321F8132FB1C00C0011C712 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = BB0001030000000011111111 /* ShareExtension-Profile.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements";
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.eu.mhsl.marianum.mobile.client.share;
|
||||||
|
DEVELOPMENT_TEAM = MY55VF3KPG;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
INFOPLIST_FILE = "Share Extension/Info.plist";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "eu.mhsl.marianum.mobile.client.Share-Extension";
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
97C147031CF9000F007C117D /* Debug */ = {
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
@@ -460,7 +816,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -509,7 +865,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -530,6 +886,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.eu.mhsl.marianum.mobile.client.share;
|
||||||
DEVELOPMENT_TEAM = MY55VF3KPG;
|
DEVELOPMENT_TEAM = MY55VF3KPG;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -539,7 +896,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "${FLUTTER_BUILD_NAME)";
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client;
|
PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@@ -560,6 +917,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.eu.mhsl.marianum.mobile.client.share;
|
||||||
DEVELOPMENT_TEAM = MY55VF3KPG;
|
DEVELOPMENT_TEAM = MY55VF3KPG;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -569,7 +927,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "${FLUTTER_BUILD_NAME)";
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client;
|
PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@@ -579,9 +937,141 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
AA01010A0000000011111111 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = BB0001040000000011111111 /* TimetableWidget-Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = TimetableWidgetExtension/TimetableWidgetExtension.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
DEVELOPMENT_TEAM = MY55VF3KPG;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
INFOPLIST_FILE = TimetableWidgetExtension/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client.TimetableWidgetExtension;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
AA01010B0000000011111111 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = BB0001050000000011111111 /* TimetableWidget-Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = TimetableWidgetExtension/TimetableWidgetExtension.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
DEVELOPMENT_TEAM = MY55VF3KPG;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
INFOPLIST_FILE = TimetableWidgetExtension/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client.TimetableWidgetExtension;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
AA01010C0000000011111111 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = BB0001060000000011111111 /* TimetableWidget-Profile.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = TimetableWidgetExtension/TimetableWidgetExtension.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
DEVELOPMENT_TEAM = MY55VF3KPG;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
INFOPLIST_FILE = TimetableWidgetExtension/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = eu.mhsl.marianum.mobile.client.TimetableWidgetExtension;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
|
3321F8152FB1C00C0011C712 /* Build configuration list for PBXNativeTarget "Share Extension" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
3321F8112FB1C00C0011C712 /* Debug */,
|
||||||
|
3321F8122FB1C00C0011C712 /* Release */,
|
||||||
|
3321F8132FB1C00C0011C712 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
@@ -602,6 +1092,16 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
AA01010D0000000011111111 /* Build configuration list for PBXNativeTarget "TimetableWidgetExtension" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
AA01010A0000000011111111 /* Debug */,
|
||||||
|
AA01010B0000000011111111 /* Release */,
|
||||||
|
AA01010C0000000011111111 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
};
|
};
|
||||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2610"
|
||||||
|
wasCreatedForAppExtension = "YES"
|
||||||
|
version = "2.0">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "3321F8042FB1C00C0011C712"
|
||||||
|
BuildableName = "Share Extension.appex"
|
||||||
|
BlueprintName = "Share Extension"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = ""
|
||||||
|
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||||
|
launchStyle = "0"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2610"
|
||||||
|
wasCreatedForAppExtension = "YES"
|
||||||
|
version = "2.0">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "AA0101010000000011111111"
|
||||||
|
BuildableName = "TimetableWidgetExtension.appex"
|
||||||
|
BlueprintName = "TimetableWidgetExtension"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = ""
|
||||||
|
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||||
|
launchStyle = "0"
|
||||||
|
askForAppToLaunch = "Yes"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
launchAutomaticallySubstyle = "2">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
import UIKit
|
|
||||||
import Flutter
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,90 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>AppGroupId</key>
|
||||||
|
<string>$(CUSTOM_GROUP_ID)</string>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Marianum Fulda</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>client</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Um Fotos direkt aus der App aufnehmen und teilen zu können wird Zugriff auf die Kamera benötigt.</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>Um Medien mit anderen zu teilen wird Zugriff zu deine Dateien benötigt.</string>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
<true/>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>Marianum Fulda</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>client</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>????</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
|
||||||
<key>LSRequiresIPhoneOS</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
|
||||||
<string>Um Medien mit anderen zu teilen wird Zugriff zu deine Dateien benötigt.</string>
|
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
<key>UIBackgroundModes</key>
|
|
||||||
<array>
|
|
||||||
<string>fetch</string>
|
|
||||||
<string>remote-notification</string>
|
|
||||||
</array>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
|
||||||
<string>LaunchScreen</string>
|
|
||||||
<key>UIMainStoryboardFile</key>
|
|
||||||
<string>Main</string>
|
|
||||||
<key>UIStatusBarHidden</key>
|
|
||||||
<false/>
|
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
</array>
|
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
||||||
<false/>
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIBackgroundModes</key>
|
||||||
|
<array>
|
||||||
|
<string>fetch</string>
|
||||||
|
<string>remote-notification</string>
|
||||||
|
</array>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UIStatusBarHidden</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
</array>
|
||||||
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -4,5 +4,10 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.eu.mhsl.marianum.mobile.client.widget</string>
|
||||||
|
<string>group.eu.mhsl.marianum.mobile.client.share</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import receive_sharing_intent
|
||||||
|
|
||||||
|
// FlutterSceneDelegate has a fallback that forwards URL events to plugins
|
||||||
|
// registered via addApplicationDelegate, but the fallback is best-effort and
|
||||||
|
// has not always fired in our setup. This subclass forwards URLs explicitly
|
||||||
|
// to receive_sharing_intent so cold-start and warm shares both reach Dart.
|
||||||
|
class SceneDelegate: FlutterSceneDelegate {
|
||||||
|
override func scene(
|
||||||
|
_ scene: UIScene,
|
||||||
|
willConnectTo session: UISceneSession,
|
||||||
|
options connectionOptions: UIScene.ConnectionOptions
|
||||||
|
) {
|
||||||
|
super.scene(scene, willConnectTo: session, options: connectionOptions)
|
||||||
|
for context in connectionOptions.urlContexts {
|
||||||
|
_ = SwiftReceiveSharingIntentPlugin.instance.application(
|
||||||
|
UIApplication.shared,
|
||||||
|
didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.url: context.url]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
|
||||||
|
for context in URLContexts {
|
||||||
|
_ = SwiftReceiveSharingIntentPlugin.instance.application(
|
||||||
|
UIApplication.shared,
|
||||||
|
open: context.url,
|
||||||
|
options: [:]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Share View Controller-->
|
||||||
|
<scene sceneID="ceB-am-kn3">
|
||||||
|
<objects>
|
||||||
|
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>AppGroupId</key>
|
||||||
|
<string>$(CUSTOM_GROUP_ID)</string>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Marianum Fulda</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionAttributes</key>
|
||||||
|
<dict>
|
||||||
|
<key>PHSupportedMediaTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>Video</string>
|
||||||
|
<string>Image</string>
|
||||||
|
</array>
|
||||||
|
<key>NSExtensionActivationRule</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionActivationSupportsText</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||||
|
<integer>10</integer>
|
||||||
|
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||||
|
<integer>10</integer>
|
||||||
|
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
||||||
|
<integer>10</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>NSExtensionMainStoryboard</key>
|
||||||
|
<string>MainInterface</string>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.share-services</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.eu.mhsl.marianum.mobile.client.share</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
import UIKit
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
// Datenmodell muss byte-für-byte zu dem passen, was
|
||||||
|
// SwiftReceiveSharingIntentPlugin auf der Host-App-Seite decodiert.
|
||||||
|
private enum SharedMediaType: String, Codable {
|
||||||
|
case image, video, text, file, url
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SharedMediaFile: Codable {
|
||||||
|
let path: String
|
||||||
|
let mimeType: String?
|
||||||
|
let thumbnail: String?
|
||||||
|
let duration: Double?
|
||||||
|
let message: String?
|
||||||
|
let type: SharedMediaType
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ShareViewController: UIViewController {
|
||||||
|
// Schlüssel sind die, die das Plugin liest.
|
||||||
|
private let userDefaultsKey = "ShareKey"
|
||||||
|
private let userDefaultsMessageKey = "ShareMessageKey"
|
||||||
|
private let urlSchemePrefix = "ShareMedia"
|
||||||
|
|
||||||
|
private var appGroupId = ""
|
||||||
|
private var hostBundleId = ""
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
resolveIds()
|
||||||
|
setupPlaceholderUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
processAttachments()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resolveIds() {
|
||||||
|
let extBundleId = Bundle.main.bundleIdentifier ?? ""
|
||||||
|
if let lastDot = extBundleId.lastIndex(of: ".") {
|
||||||
|
hostBundleId = String(extBundleId[..<lastDot])
|
||||||
|
}
|
||||||
|
let custom = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as? String
|
||||||
|
if let custom, !custom.isEmpty, !custom.contains("$(") {
|
||||||
|
appGroupId = custom
|
||||||
|
} else {
|
||||||
|
appGroupId = "group.\(hostBundleId)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupPlaceholderUI() {
|
||||||
|
view.backgroundColor = .systemBackground
|
||||||
|
let spinner = UIActivityIndicatorView(style: .medium)
|
||||||
|
spinner.startAnimating()
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = "Wird geteilt …"
|
||||||
|
label.font = .preferredFont(forTextStyle: .footnote)
|
||||||
|
label.textColor = .secondaryLabel
|
||||||
|
let stack = UIStackView(arrangedSubviews: [spinner, label])
|
||||||
|
stack.axis = .vertical
|
||||||
|
stack.alignment = .center
|
||||||
|
stack.spacing = 8
|
||||||
|
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.addSubview(stack)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||||
|
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
private func processAttachments() {
|
||||||
|
let attachments: [NSItemProvider] = (extensionContext?.inputItems ?? [])
|
||||||
|
.compactMap { $0 as? NSExtensionItem }
|
||||||
|
.flatMap { $0.attachments ?? [] }
|
||||||
|
guard !attachments.isEmpty else {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let group = DispatchGroup()
|
||||||
|
var collected: [SharedMediaFile] = []
|
||||||
|
let lock = NSLock()
|
||||||
|
for provider in attachments {
|
||||||
|
group.enter()
|
||||||
|
handle(provider: provider) { file in
|
||||||
|
if let f = file {
|
||||||
|
lock.lock(); collected.append(f); lock.unlock()
|
||||||
|
}
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.notify(queue: .main) { [weak self] in
|
||||||
|
self?.saveAndRedirect(items: collected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reihenfolge entspricht SharedMediaType.allCases im Plugin —
|
||||||
|
// spezifische Typen (image, video) vor generischen (file, url).
|
||||||
|
private func handle(provider: NSItemProvider,
|
||||||
|
completion: @escaping (SharedMediaFile?) -> Void) {
|
||||||
|
let order: [(String, SharedMediaType)] = [
|
||||||
|
(UTType.image.identifier, .image),
|
||||||
|
(UTType.movie.identifier, .video),
|
||||||
|
(UTType.text.identifier, .text),
|
||||||
|
(UTType.fileURL.identifier, .file),
|
||||||
|
(UTType.url.identifier, .url),
|
||||||
|
(UTType.data.identifier, .file),
|
||||||
|
]
|
||||||
|
for (typeId, kind) in order {
|
||||||
|
if provider.hasItemConformingToTypeIdentifier(typeId) {
|
||||||
|
provider.loadItem(forTypeIdentifier: typeId) { [weak self] data, error in
|
||||||
|
guard let self else { completion(nil); return }
|
||||||
|
if error != nil { completion(nil); return }
|
||||||
|
completion(self.toSharedFile(data: data, kind: kind))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completion(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func toSharedFile(data: Any?, kind: SharedMediaType) -> SharedMediaFile? {
|
||||||
|
switch kind {
|
||||||
|
case .text:
|
||||||
|
guard let s = data as? String else { return nil }
|
||||||
|
return SharedMediaFile(path: s, mimeType: "text/plain",
|
||||||
|
thumbnail: nil, duration: nil, message: nil, type: .text)
|
||||||
|
case .url:
|
||||||
|
guard let u = data as? URL else { return nil }
|
||||||
|
return SharedMediaFile(path: u.absoluteString, mimeType: nil,
|
||||||
|
thumbnail: nil, duration: nil, message: nil, type: .url)
|
||||||
|
case .image:
|
||||||
|
if let u = data as? URL, let dst = copyIntoAppGroup(src: u) {
|
||||||
|
return SharedMediaFile(path: pathString(for: dst), mimeType: mime(for: u),
|
||||||
|
thumbnail: nil, duration: nil, message: nil, type: .image)
|
||||||
|
}
|
||||||
|
if let img = data as? UIImage, let dst = writePng(image: img) {
|
||||||
|
return SharedMediaFile(path: pathString(for: dst), mimeType: "image/png",
|
||||||
|
thumbnail: nil, duration: nil, message: nil, type: .image)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case .video:
|
||||||
|
guard let u = data as? URL, let dst = copyIntoAppGroup(src: u) else { return nil }
|
||||||
|
return SharedMediaFile(path: pathString(for: dst), mimeType: mime(for: u),
|
||||||
|
thumbnail: nil, duration: videoDurationMs(url: u),
|
||||||
|
message: nil, type: .video)
|
||||||
|
case .file:
|
||||||
|
guard let u = data as? URL, let dst = copyIntoAppGroup(src: u) else { return nil }
|
||||||
|
return SharedMediaFile(path: pathString(for: dst), mimeType: mime(for: u),
|
||||||
|
thumbnail: nil, duration: nil, message: nil, type: .file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func copyIntoAppGroup(src: URL) -> URL? {
|
||||||
|
guard let container = FileManager.default
|
||||||
|
.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let name = src.lastPathComponent.isEmpty ? UUID().uuidString : src.lastPathComponent
|
||||||
|
let dst = container.appendingPathComponent(name)
|
||||||
|
do {
|
||||||
|
if FileManager.default.fileExists(atPath: dst.path) {
|
||||||
|
try FileManager.default.removeItem(at: dst)
|
||||||
|
}
|
||||||
|
try FileManager.default.copyItem(at: src, to: dst)
|
||||||
|
return dst
|
||||||
|
} catch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func writePng(image: UIImage) -> URL? {
|
||||||
|
guard let container = FileManager.default
|
||||||
|
.containerURL(forSecurityApplicationGroupIdentifier: appGroupId),
|
||||||
|
let data = image.pngData() else { return nil }
|
||||||
|
let dst = container.appendingPathComponent("\(UUID().uuidString).png")
|
||||||
|
return (try? data.write(to: dst)) != nil ? dst : nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Das Plugin macht `path.replacingOccurrences(of: "file://", with: "")`,
|
||||||
|
// also liefern wir absoluteString mit Prozent-Decoding — selbes Format wie
|
||||||
|
// im Original-RSIShareViewController.
|
||||||
|
private func pathString(for url: URL) -> String {
|
||||||
|
url.absoluteString.removingPercentEncoding ?? url.absoluteString
|
||||||
|
}
|
||||||
|
|
||||||
|
private func mime(for url: URL) -> String? {
|
||||||
|
UTType(filenameExtension: url.pathExtension)?.preferredMIMEType
|
||||||
|
}
|
||||||
|
|
||||||
|
private func videoDurationMs(url: URL) -> Double {
|
||||||
|
(CMTimeGetSeconds(AVURLAsset(url: url).duration) * 1000).rounded()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func saveAndRedirect(items: [SharedMediaFile]) {
|
||||||
|
guard !items.isEmpty else {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let defaults = UserDefaults(suiteName: appGroupId)
|
||||||
|
guard let encoded = try? JSONEncoder().encode(items) else {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defaults?.set(encoded, forKey: userDefaultsKey)
|
||||||
|
defaults?.removeObject(forKey: userDefaultsMessageKey)
|
||||||
|
|
||||||
|
let urlStr = "\(urlSchemePrefix)-\(hostBundleId):share"
|
||||||
|
guard let url = URL(string: urlStr) else {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apple-DTS says Share Extensions are not officially allowed to open
|
||||||
|
// URLs. Both `extensionContext.open` and the responder-chain trick
|
||||||
|
// succeed in some iOS versions and fail silently in others, so we fire
|
||||||
|
// both in parallel and let whichever Apple is honouring this release
|
||||||
|
// win the race.
|
||||||
|
extensionContext?.open(url, completionHandler: nil)
|
||||||
|
var responder: UIResponder? = self
|
||||||
|
while responder != nil {
|
||||||
|
if let app = responder as? UIApplication {
|
||||||
|
app.open(url, options: [:], completionHandler: nil)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
responder = responder?.next
|
||||||
|
}
|
||||||
|
|
||||||
|
// Brief window so the open request reaches the system before we tear
|
||||||
|
// the extension down.
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||||
|
self?.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func finish() {
|
||||||
|
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "marianum_m_white.svg",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"preserves-vector-representation" : true,
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="70.000168mm"
|
||||||
|
height="82.227348mm"
|
||||||
|
viewBox="0 0 70.000168 82.227348"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||||
|
id="defs1" /><g
|
||||||
|
id="g1"
|
||||||
|
transform="matrix(0.26458333,0,0,0.26458334,107.44411,-80.482198)"><g
|
||||||
|
id="group-R5"
|
||||||
|
transform="translate(-749.41293,290.52252)"><path
|
||||||
|
id="path3"
|
||||||
|
d="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"
|
||||||
|
style="fill:#d3d2d2;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||||
|
transform="matrix(0.13333333,0,0,-0.13333333,0,632.14667)" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Marianum Stundenplan</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(MARKETING_VERSION)</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.widgetkit-extension</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// Marianum-M peeking out of the bottom-right corner. Sized to the longer
|
||||||
|
/// widget edge so it scales with the picked WidgetFamily; offset nudges a
|
||||||
|
/// sliver behind the edge. Opacity comes from the widget palette so the
|
||||||
|
/// watermark matches Android's `watermarkAlpha` at the same brightness.
|
||||||
|
struct MarianumWatermark: View {
|
||||||
|
@Environment(\.widgetPalette) private var palette
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geo in
|
||||||
|
let markSize = min(400, max(160, max(geo.size.width, geo.size.height) * 0.8))
|
||||||
|
let offsetX = markSize * 0.18
|
||||||
|
let offsetY = markSize * 0.18
|
||||||
|
ZStack(alignment: .bottomTrailing) {
|
||||||
|
Color.clear
|
||||||
|
Image("marianum_m")
|
||||||
|
.resizable()
|
||||||
|
.renderingMode(.template)
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.foregroundStyle(palette.textPrimary)
|
||||||
|
.frame(width: markSize, height: markSize)
|
||||||
|
.opacity(palette.watermarkOpacity)
|
||||||
|
.offset(x: offsetX, y: offsetY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.clipped()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,424 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
// Layout constants — must mirror WidgetRenderer.kt on Android, otherwise
|
||||||
|
// the platforms drift apart on the same widget size.
|
||||||
|
let FALLBACK_VIRTUAL_MINUTES = 11 * 60
|
||||||
|
let MIN_HOUR_HEIGHT: CGFloat = 18
|
||||||
|
let MAX_HOUR_HEIGHT: CGFloat = 72
|
||||||
|
let MIN_BLOCK_HEIGHT: CGFloat = 16
|
||||||
|
let LESSON_GAP: CGFloat = 1.5
|
||||||
|
|
||||||
|
func realMinutesToVirtual(_ realMin: Int, periods: [WidgetPeriod]) -> CGFloat {
|
||||||
|
guard !periods.isEmpty else { return CGFloat(realMin) }
|
||||||
|
for p in periods where realMin >= p.startMinutes && realMin <= p.endMinutes {
|
||||||
|
return CGFloat(p.virtualStartMinutes + (realMin - p.startMinutes))
|
||||||
|
}
|
||||||
|
let first = periods.first!
|
||||||
|
if realMin < first.startMinutes {
|
||||||
|
return CGFloat(realMin - first.startMinutes + first.virtualStartMinutes)
|
||||||
|
}
|
||||||
|
let last = periods.last!
|
||||||
|
if realMin > last.endMinutes {
|
||||||
|
return CGFloat(last.virtualEndMinutes + (realMin - last.endMinutes))
|
||||||
|
}
|
||||||
|
var prev = first
|
||||||
|
for i in 1..<periods.count {
|
||||||
|
let curr = periods[i]
|
||||||
|
if realMin > prev.endMinutes && realMin < curr.startMinutes {
|
||||||
|
let gap = curr.startMinutes - prev.endMinutes
|
||||||
|
let virtualGap = curr.virtualStartMinutes - prev.virtualEndMinutes
|
||||||
|
if gap > 0 {
|
||||||
|
return CGFloat(prev.virtualEndMinutes) +
|
||||||
|
CGFloat(realMin - prev.endMinutes) * CGFloat(virtualGap) / CGFloat(gap)
|
||||||
|
}
|
||||||
|
return CGFloat(curr.virtualStartMinutes)
|
||||||
|
}
|
||||||
|
prev = curr
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
let BLOCK_SHOW_ROOM_MIN: CGFloat = 18
|
||||||
|
let BLOCK_SHOW_TEACHER_SEPARATE_MIN: CGFloat = 30
|
||||||
|
|
||||||
|
let MIN_SUBJECT_FONT: CGFloat = 9
|
||||||
|
let MAX_SUBJECT_FONT: CGFloat = 14
|
||||||
|
let MIN_SECONDARY_FONT: CGFloat = 7
|
||||||
|
|
||||||
|
func subjectFont(forHourHeight hourHeight: CGFloat) -> CGFloat {
|
||||||
|
let t = max(0, min(1, (hourHeight - MIN_HOUR_HEIGHT) / (MAX_HOUR_HEIGHT - MIN_HOUR_HEIGHT)))
|
||||||
|
return MIN_SUBJECT_FONT + t * (MAX_SUBJECT_FONT - MIN_SUBJECT_FONT)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimetableDayView: View {
|
||||||
|
let entry: TimetableEntry
|
||||||
|
@Environment(\.widgetPalette) private var palette
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
if !entry.isLoggedIn {
|
||||||
|
placeholder("Bitte einloggen, um den Stundenplan zu laden")
|
||||||
|
} else if let data = entry.data {
|
||||||
|
content(data: data)
|
||||||
|
} else {
|
||||||
|
placeholder("Lade…")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(MarianumWatermark())
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func content(data: WidgetTimetableData) -> some View {
|
||||||
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
|
header(data: data)
|
||||||
|
if data.isHoliday {
|
||||||
|
emptyState(text: data.holidayName ?? "Ferien")
|
||||||
|
} else if data.lessons.isEmpty {
|
||||||
|
emptyState(text: "Keine Stunden")
|
||||||
|
} else {
|
||||||
|
GeometryReader { geo in
|
||||||
|
let totalMin = CGFloat(data.periods.last?.virtualEndMinutes ?? FALLBACK_VIRTUAL_MINUTES)
|
||||||
|
TimeGridView(
|
||||||
|
lessons: data.lessons,
|
||||||
|
periods: data.periods,
|
||||||
|
hourHeight: max(
|
||||||
|
MIN_HOUR_HEIGHT,
|
||||||
|
min(MAX_HOUR_HEIGHT, geo.size.height / max(totalMin, 60) * 60)
|
||||||
|
),
|
||||||
|
showRoom: true,
|
||||||
|
showTeacher: true,
|
||||||
|
showTimeLabels: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func header(data: WidgetTimetableData) -> some View {
|
||||||
|
HStack {
|
||||||
|
Text(dayLabel(for: data.anchorDate))
|
||||||
|
.font(.system(size: 14, weight: .semibold))
|
||||||
|
.foregroundStyle(palette.textPrimary)
|
||||||
|
Spacer()
|
||||||
|
Text("Stand: \(freshnessLabel(for: data.fetchedAt))")
|
||||||
|
.font(.system(size: 10))
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func emptyState(text: String) -> some View {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
Text(text)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func placeholder(_ message: String) -> some View {
|
||||||
|
VStack(spacing: 4) {
|
||||||
|
Text("Marianum Stundenplan")
|
||||||
|
.font(.system(size: 14, weight: .semibold))
|
||||||
|
.foregroundStyle(palette.textPrimary)
|
||||||
|
Text(message)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimeGridView: View {
|
||||||
|
let lessons: [WidgetLesson]
|
||||||
|
let periods: [WidgetPeriod]
|
||||||
|
let hourHeight: CGFloat
|
||||||
|
let showRoom: Bool
|
||||||
|
let showTeacher: Bool
|
||||||
|
let showTimeLabels: Bool
|
||||||
|
/// Week-widget passes 3 for narrow columns; day-widget keeps 7.
|
||||||
|
var horizontalPadding: CGFloat = 7
|
||||||
|
|
||||||
|
@Environment(\.widgetPalette) private var palette
|
||||||
|
|
||||||
|
private var totalVirtualMinutes: Int {
|
||||||
|
periods.last?.virtualEndMinutes ?? FALLBACK_VIRTUAL_MINUTES
|
||||||
|
}
|
||||||
|
|
||||||
|
private var totalHeight: CGFloat {
|
||||||
|
CGFloat(totalVirtualMinutes) * hourHeight / 60.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Below this per-hour height the two-line label collapses to a single
|
||||||
|
/// period number — time + number overlap otherwise.
|
||||||
|
private var compactLabels: Bool { hourHeight < 26 }
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(alignment: .top, spacing: 0) {
|
||||||
|
if showTimeLabels {
|
||||||
|
// 28pt matches the Week widget's time-label column so the
|
||||||
|
// visual rhythm is identical across both widgets.
|
||||||
|
timeLabelsColumn
|
||||||
|
.frame(width: 28, alignment: .topTrailing)
|
||||||
|
Rectangle()
|
||||||
|
.fill(palette.divider)
|
||||||
|
.frame(width: 1)
|
||||||
|
}
|
||||||
|
ZStack(alignment: .top) {
|
||||||
|
gridLines
|
||||||
|
breakBlocks
|
||||||
|
ForEach(lessons.indices, id: \.self) { idx in
|
||||||
|
lessonBlock(lessons[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, minHeight: totalHeight, alignment: .top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var timeLabelsColumn: some View {
|
||||||
|
ZStack(alignment: .topTrailing) {
|
||||||
|
ForEach(periodBoundaries(periods), id: \.self) { virtualMin in
|
||||||
|
Rectangle()
|
||||||
|
.fill(palette.divider)
|
||||||
|
.frame(height: 1)
|
||||||
|
.offset(y: CGFloat(virtualMin) * hourHeight / 60.0)
|
||||||
|
}
|
||||||
|
ForEach(periods, id: \.startMinutes) { period in
|
||||||
|
VStack(alignment: .trailing, spacing: -2) {
|
||||||
|
if compactLabels {
|
||||||
|
Text("\(period.name).")
|
||||||
|
.font(.system(size: 9, weight: .bold))
|
||||||
|
.foregroundStyle(palette.textPrimary)
|
||||||
|
.lineLimit(1)
|
||||||
|
} else {
|
||||||
|
Text(formatHm(period.startMinutes))
|
||||||
|
.font(.system(size: 9))
|
||||||
|
.foregroundStyle(palette.textPrimary)
|
||||||
|
.lineLimit(1)
|
||||||
|
Text("\(period.name).")
|
||||||
|
.font(.system(size: 7, weight: .bold))
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.trailing, 4)
|
||||||
|
.offset(y: CGFloat(period.virtualStartMinutes) * hourHeight / 60.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: totalHeight, alignment: .topTrailing)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var gridLines: some View {
|
||||||
|
ZStack(alignment: .top) {
|
||||||
|
ForEach(periodBoundaries(periods), id: \.self) { virtualMin in
|
||||||
|
Rectangle()
|
||||||
|
.fill(palette.divider)
|
||||||
|
.frame(height: 1)
|
||||||
|
.offset(y: CGFloat(virtualMin) * hourHeight / 60.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: totalHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var breakBlocks: some View {
|
||||||
|
ZStack(alignment: .top) {
|
||||||
|
ForEach(0..<max(0, periods.count - 1), id: \.self) { i in
|
||||||
|
let curr = periods[i]
|
||||||
|
let next = periods[i + 1]
|
||||||
|
let virtualGap = next.virtualStartMinutes - curr.virtualEndMinutes
|
||||||
|
if virtualGap > 0 {
|
||||||
|
Rectangle()
|
||||||
|
.fill(palette.breakBlock)
|
||||||
|
.frame(height: CGFloat(virtualGap) * hourHeight / 60.0)
|
||||||
|
.padding(.horizontal, 1)
|
||||||
|
.offset(y: CGFloat(curr.virtualEndMinutes) * hourHeight / 60.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: totalHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func formatHm(_ minutes: Int) -> String {
|
||||||
|
String(format: "%02d:%02d", minutes / 60, minutes % 60)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func lessonBlock(_ lesson: WidgetLesson) -> some View {
|
||||||
|
let cal = Calendar.current
|
||||||
|
let comps = cal.dateComponents([.hour, .minute], from: lesson.start)
|
||||||
|
let startMinutes = (comps.hour ?? 0) * 60 + (comps.minute ?? 0)
|
||||||
|
let durationMinutes = max(15, Int(lesson.end.timeIntervalSince(lesson.start) / 60))
|
||||||
|
let virtualStart = realMinutesToVirtual(startMinutes, periods: periods)
|
||||||
|
let virtualEnd = realMinutesToVirtual(startMinutes + durationMinutes, periods: periods)
|
||||||
|
|
||||||
|
if virtualEnd > virtualStart {
|
||||||
|
let top = virtualStart * hourHeight / 60.0 + LESSON_GAP / 2
|
||||||
|
let height = max(
|
||||||
|
MIN_BLOCK_HEIGHT,
|
||||||
|
(virtualEnd - virtualStart) * hourHeight / 60.0 - LESSON_GAP
|
||||||
|
)
|
||||||
|
let subjectSize = subjectFont(forHourHeight: hourHeight)
|
||||||
|
let secondarySize = max(MIN_SECONDARY_FONT, subjectSize - 2)
|
||||||
|
let room = lesson.room
|
||||||
|
let teacher = lesson.teacher ?? lesson.originalTeacher
|
||||||
|
let hasSecondary = (room?.isEmpty == false) || (teacher?.isEmpty == false)
|
||||||
|
HStack(alignment: .top, spacing: 4) {
|
||||||
|
Text(subjectLabel(lesson))
|
||||||
|
.font(.system(size: subjectSize, weight: .semibold))
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.lineLimit(hasSecondary ? 1 : 2)
|
||||||
|
.minimumScaleFactor(0.5)
|
||||||
|
if hasSecondary {
|
||||||
|
Spacer(minLength: 0)
|
||||||
|
VStack(alignment: .trailing, spacing: -1) {
|
||||||
|
if showRoom && height >= BLOCK_SHOW_ROOM_MIN {
|
||||||
|
if let room, !room.isEmpty {
|
||||||
|
Text(room)
|
||||||
|
.font(.system(size: secondarySize))
|
||||||
|
.foregroundStyle(.white.opacity(0.85))
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.5)
|
||||||
|
}
|
||||||
|
if showTeacher,
|
||||||
|
height >= BLOCK_SHOW_TEACHER_SEPARATE_MIN,
|
||||||
|
let teacher,
|
||||||
|
!teacher.isEmpty {
|
||||||
|
Text(teacher)
|
||||||
|
.font(.system(size: secondarySize))
|
||||||
|
.foregroundStyle(.white.opacity(0.7))
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, horizontalPadding)
|
||||||
|
.padding(.vertical, 3)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||||
|
.frame(height: height, alignment: .topLeading)
|
||||||
|
.background(blockColor(lesson))
|
||||||
|
.cornerRadius(6)
|
||||||
|
.overlay(alignment: .bottomLeading) {
|
||||||
|
// Separate fixed-size badge so the +N hint stays readable
|
||||||
|
// when the subject autoshrinks on narrow tiles.
|
||||||
|
if let count = lesson.siblingCount, count > 0 {
|
||||||
|
Text("+\(count)")
|
||||||
|
.font(.system(size: 12, weight: .bold))
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.padding(.leading, horizontalPadding)
|
||||||
|
.padding(.bottom, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.overlay {
|
||||||
|
// CrossPainter parity: clip cross to the rounded shape so
|
||||||
|
// the diagonals don't bleed past the corners.
|
||||||
|
if lesson.status == .cancelled {
|
||||||
|
ZStack {
|
||||||
|
RoundedRectangle(cornerRadius: 6)
|
||||||
|
.stroke(Color.red.opacity(0.78), lineWidth: 1.5)
|
||||||
|
GeometryReader { geo in
|
||||||
|
Path { p in
|
||||||
|
p.move(to: .zero)
|
||||||
|
p.addLine(to: CGPoint(x: geo.size.width, y: geo.size.height))
|
||||||
|
p.move(to: CGPoint(x: geo.size.width, y: 0))
|
||||||
|
p.addLine(to: CGPoint(x: 0, y: geo.size.height))
|
||||||
|
}
|
||||||
|
.stroke(Color.red.opacity(0.78), lineWidth: 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, showRoom ? 2 : 1)
|
||||||
|
.offset(y: top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func subjectLabel(_ lesson: WidgetLesson) -> String {
|
||||||
|
!lesson.subjectShort.isEmpty
|
||||||
|
? lesson.subjectShort
|
||||||
|
: (lesson.subjectLong ?? "—")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mirrors lesson_color.dart + custom_event_colors.dart so the widget
|
||||||
|
/// matches the in-app calendar exactly.
|
||||||
|
private func blockColor(_ lesson: WidgetLesson) -> Color {
|
||||||
|
if lesson.status == .event, let custom = lesson.customColor {
|
||||||
|
switch custom {
|
||||||
|
case "orange": return Color(red: 239/255.0, green: 108/255.0, blue: 0/255.0)
|
||||||
|
case "red": return Color(red: 153/255.0, green: 51/255.0, blue: 51/255.0)
|
||||||
|
case "green": return Color(red: 76/255.0, green: 175/255.0, blue: 80/255.0)
|
||||||
|
case "blue": return Color(red: 33/255.0, green: 150/255.0, blue: 243/255.0)
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch lesson.status {
|
||||||
|
case .regular, .past: return Color(red: 153/255.0, green: 51/255.0, blue: 51/255.0)
|
||||||
|
case .ongoing: return Color(red: 200/255.0, green: 51/255.0, blue: 51/255.0)
|
||||||
|
case .cancelled: return .black
|
||||||
|
case .irregular: return Color(red: 143/255.0, green: 25/255.0, blue: 179/255.0)
|
||||||
|
case .teacherChanged: return Color(red: 41/255.0, green: 99/255.0, blue: 155/255.0)
|
||||||
|
case .event: return Color(red: 239/255.0, green: 108/255.0, blue: 0/255.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Period boundaries deduped: adjacent periods share a line, periods on
|
||||||
|
/// either side of a break get their own (bracketing the break block).
|
||||||
|
func periodBoundaries(_ periods: [WidgetPeriod]) -> [Int] {
|
||||||
|
var seen = Set<Int>()
|
||||||
|
var result: [Int] = []
|
||||||
|
for p in periods {
|
||||||
|
for v in [p.virtualStartMinutes, p.virtualEndMinutes] {
|
||||||
|
if seen.insert(v).inserted { result.append(v) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.sorted()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dayLabel(for date: Date) -> String {
|
||||||
|
let cal = Calendar.current
|
||||||
|
let today = cal.startOfDay(for: Date())
|
||||||
|
let anchor = cal.startOfDay(for: date)
|
||||||
|
if anchor == today {
|
||||||
|
return "Heute · \(shortDate(date))"
|
||||||
|
}
|
||||||
|
if let tomorrow = cal.date(byAdding: .day, value: 1, to: today), anchor == tomorrow {
|
||||||
|
return "Morgen · \(shortDate(date))"
|
||||||
|
}
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.locale = Locale(identifier: "de_DE")
|
||||||
|
formatter.dateFormat = "EEEE · dd.MM."
|
||||||
|
return formatter.string(from: date)
|
||||||
|
}
|
||||||
|
|
||||||
|
func shortDate(_ date: Date) -> String {
|
||||||
|
let f = DateFormatter()
|
||||||
|
f.locale = Locale(identifier: "de_DE")
|
||||||
|
f.dateFormat = "dd.MM."
|
||||||
|
return f.string(from: date)
|
||||||
|
}
|
||||||
|
|
||||||
|
func freshnessLabel(for fetchedAt: Date) -> String {
|
||||||
|
let cal = Calendar.current
|
||||||
|
let today = cal.startOfDay(for: Date())
|
||||||
|
let fetchedDay = cal.startOfDay(for: fetchedAt)
|
||||||
|
let timeFmt = DateFormatter()
|
||||||
|
timeFmt.locale = Locale(identifier: "de_DE")
|
||||||
|
timeFmt.dateFormat = "HH:mm"
|
||||||
|
if fetchedDay == today {
|
||||||
|
return timeFmt.string(from: fetchedAt)
|
||||||
|
}
|
||||||
|
if let yesterday = cal.date(byAdding: .day, value: -1, to: today),
|
||||||
|
fetchedDay == yesterday {
|
||||||
|
return "gestern \(timeFmt.string(from: fetchedAt))"
|
||||||
|
}
|
||||||
|
let dateTimeFmt = DateFormatter()
|
||||||
|
dateTimeFmt.locale = Locale(identifier: "de_DE")
|
||||||
|
dateTimeFmt.dateFormat = "dd.MM. HH:mm"
|
||||||
|
return dateTimeFmt.string(from: fetchedAt)
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
struct TimetableWeekView: View {
|
||||||
|
let entry: TimetableEntry
|
||||||
|
@Environment(\.widgetPalette) private var palette
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
if !entry.isLoggedIn {
|
||||||
|
placeholder("Bitte einloggen, um den Stundenplan zu laden")
|
||||||
|
} else if let data = entry.data {
|
||||||
|
content(data: data)
|
||||||
|
} else {
|
||||||
|
placeholder("Lade…")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(MarianumWatermark())
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func content(data: WidgetTimetableData) -> some View {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
header(data: data)
|
||||||
|
dayHeaderRow(data: data)
|
||||||
|
GeometryReader { geo in
|
||||||
|
let totalMin = CGFloat(data.periods.last?.virtualEndMinutes ?? FALLBACK_VIRTUAL_MINUTES)
|
||||||
|
let hourHeight = max(
|
||||||
|
MIN_HOUR_HEIGHT,
|
||||||
|
min(MAX_HOUR_HEIGHT, geo.size.height / max(totalMin, 60) * 60)
|
||||||
|
)
|
||||||
|
let dayColumnWidth = (geo.size.width - 28 - 4) / 5
|
||||||
|
let subjectOnly = dayColumnWidth < 70
|
||||||
|
HStack(alignment: .top, spacing: 0) {
|
||||||
|
timeLabelsColumn(hourHeight: hourHeight, periods: data.periods)
|
||||||
|
.frame(width: 28, alignment: .topTrailing)
|
||||||
|
columnDivider
|
||||||
|
ForEach(0..<5, id: \.self) { offset in
|
||||||
|
column(
|
||||||
|
data: data,
|
||||||
|
offset: offset,
|
||||||
|
hourHeight: hourHeight,
|
||||||
|
subjectOnly: subjectOnly
|
||||||
|
)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
if offset < 4 { columnDivider }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var columnDivider: some View {
|
||||||
|
Rectangle()
|
||||||
|
.fill(palette.divider)
|
||||||
|
.frame(width: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func header(data: WidgetTimetableData) -> some View {
|
||||||
|
let cal = Calendar.current
|
||||||
|
let week = cal.component(.weekOfYear, from: data.anchorDate)
|
||||||
|
let endDate = cal.date(byAdding: .day, value: 4, to: data.anchorDate) ?? data.anchorDate
|
||||||
|
return HStack {
|
||||||
|
Text("KW \(week) · \(shortDate(data.anchorDate))–\(shortDate(endDate))")
|
||||||
|
.font(.system(size: 13, weight: .semibold))
|
||||||
|
.foregroundStyle(palette.textPrimary)
|
||||||
|
Spacer()
|
||||||
|
Text("Stand: \(freshnessLabel(for: data.fetchedAt))")
|
||||||
|
.font(.system(size: 10))
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dayHeaderRow(data: WidgetTimetableData) -> some View {
|
||||||
|
let cal = Calendar.current
|
||||||
|
// `.frame(maxWidth: .infinity)` is critical: without an explicit
|
||||||
|
// greedy width (or a true `Spacer()` inside), this HStack collapses
|
||||||
|
// to the sum of its fixed-width children in a `.leading` VStack
|
||||||
|
// and the 5 day-columns end up sharing ~5pt of width — the labels
|
||||||
|
// crunch to the left and stop aligning with the grid columns below.
|
||||||
|
// The fixed height keeps `columnDivider`'s vertically-flexible
|
||||||
|
// Rectangle from stealing space from the GeometryReader.
|
||||||
|
return HStack(spacing: 0) {
|
||||||
|
Spacer().frame(width: 28)
|
||||||
|
columnDivider
|
||||||
|
ForEach(0..<5, id: \.self) { offset in
|
||||||
|
let day = cal.date(byAdding: .day, value: offset, to: data.anchorDate) ?? data.anchorDate
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Text(weekday(for: day))
|
||||||
|
.font(.system(size: 11, weight: .bold))
|
||||||
|
.foregroundStyle(palette.textPrimary)
|
||||||
|
Text(shortDate(day))
|
||||||
|
.font(.system(size: 9))
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
if offset < 4 { columnDivider }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, minHeight: 26, maxHeight: 26)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func timeLabelsColumn(hourHeight: CGFloat, periods: [WidgetPeriod]) -> some View {
|
||||||
|
let totalMin = periods.last?.virtualEndMinutes ?? FALLBACK_VIRTUAL_MINUTES
|
||||||
|
let totalHeight = CGFloat(totalMin) * hourHeight / 60.0
|
||||||
|
return ZStack(alignment: .topTrailing) {
|
||||||
|
ForEach(periodBoundaries(periods), id: \.self) { virtualMin in
|
||||||
|
Rectangle()
|
||||||
|
.fill(palette.divider)
|
||||||
|
.frame(height: 1)
|
||||||
|
.offset(y: CGFloat(virtualMin) * hourHeight / 60.0)
|
||||||
|
}
|
||||||
|
ForEach(periods, id: \.startMinutes) { period in
|
||||||
|
VStack(alignment: .trailing, spacing: -2) {
|
||||||
|
Text(String(format: "%02d:%02d", period.startMinutes / 60, period.startMinutes % 60))
|
||||||
|
.font(.system(size: 8))
|
||||||
|
.foregroundStyle(palette.textPrimary)
|
||||||
|
.lineLimit(1)
|
||||||
|
Text("\(period.name).")
|
||||||
|
.font(.system(size: 6, weight: .bold))
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
.offset(y: CGFloat(period.virtualStartMinutes) * hourHeight / 60.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: totalHeight, alignment: .topTrailing)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func column(
|
||||||
|
data: WidgetTimetableData,
|
||||||
|
offset: Int,
|
||||||
|
hourHeight: CGFloat,
|
||||||
|
subjectOnly: Bool
|
||||||
|
) -> some View {
|
||||||
|
let cal = Calendar.current
|
||||||
|
let day = cal.date(byAdding: .day, value: offset, to: data.anchorDate) ?? data.anchorDate
|
||||||
|
let lessonsForDay = data.lessons.filter { cal.isDate($0.start, inSameDayAs: day) }
|
||||||
|
return TimeGridView(
|
||||||
|
lessons: lessonsForDay,
|
||||||
|
periods: data.periods,
|
||||||
|
hourHeight: hourHeight,
|
||||||
|
showRoom: !subjectOnly,
|
||||||
|
showTeacher: !subjectOnly,
|
||||||
|
showTimeLabels: false,
|
||||||
|
horizontalPadding: 3
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func weekday(for date: Date) -> String {
|
||||||
|
let f = DateFormatter()
|
||||||
|
f.locale = Locale(identifier: "de_DE")
|
||||||
|
f.dateFormat = "EE"
|
||||||
|
return f.string(from: date)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func placeholder(_ message: String) -> some View {
|
||||||
|
VStack(spacing: 4) {
|
||||||
|
Text("Marianum Stundenplan")
|
||||||
|
.font(.system(size: 14, weight: .semibold))
|
||||||
|
.foregroundStyle(palette.textPrimary)
|
||||||
|
Text(message)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.eu.mhsl.marianum.mobile.client.widget</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import WidgetKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct MarianumWidgetBundle: WidgetBundle {
|
||||||
|
var body: some Widget {
|
||||||
|
TimetableDayWidget()
|
||||||
|
TimetableWeekWidget()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Day widget
|
||||||
|
|
||||||
|
struct TimetableDayWidget: Widget {
|
||||||
|
let kind: String = "TimetableDayWidget"
|
||||||
|
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
StaticConfiguration(kind: kind, provider: TimetableDayProvider()) { entry in
|
||||||
|
TimetableDayView(entry: entry).widgetSurface(entry: entry)
|
||||||
|
}
|
||||||
|
.configurationDisplayName("Marianum · Heute")
|
||||||
|
.description("Stundenplan und Vertretungen für den anstehenden Schultag.")
|
||||||
|
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimetableDayProvider: TimelineProvider {
|
||||||
|
func placeholder(in context: Context) -> TimetableEntry {
|
||||||
|
TimetableEntry.placeholder()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSnapshot(in context: Context, completion: @escaping (TimetableEntry) -> Void) {
|
||||||
|
completion(TimetableEntry.current(variant: .day))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeline(
|
||||||
|
in context: Context,
|
||||||
|
completion: @escaping (Timeline<TimetableEntry>) -> Void
|
||||||
|
) {
|
||||||
|
let entry = TimetableEntry.current(variant: .day)
|
||||||
|
// 30 min mirrors the Dart workmanager cadence. iOS treats this as
|
||||||
|
// advisory; the "Stand:" label tells the user when data is stale.
|
||||||
|
let next = Calendar.current.date(byAdding: .minute, value: 30, to: Date()) ?? Date()
|
||||||
|
completion(Timeline(entries: [entry], policy: .after(next)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Week widget
|
||||||
|
|
||||||
|
struct TimetableWeekWidget: Widget {
|
||||||
|
let kind: String = "TimetableWeekWidget"
|
||||||
|
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
StaticConfiguration(kind: kind, provider: TimetableWeekProvider()) { entry in
|
||||||
|
TimetableWeekView(entry: entry).widgetSurface(entry: entry)
|
||||||
|
}
|
||||||
|
.configurationDisplayName("Marianum · Woche")
|
||||||
|
.description("Stundenplan und Vertretungen für die ganze Schulwoche.")
|
||||||
|
.supportedFamilies([.systemMedium, .systemLarge, .systemExtraLarge])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimetableWeekProvider: TimelineProvider {
|
||||||
|
func placeholder(in context: Context) -> TimetableEntry {
|
||||||
|
TimetableEntry.placeholder()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSnapshot(in context: Context, completion: @escaping (TimetableEntry) -> Void) {
|
||||||
|
completion(TimetableEntry.current(variant: .week))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeline(
|
||||||
|
in context: Context,
|
||||||
|
completion: @escaping (Timeline<TimetableEntry>) -> Void
|
||||||
|
) {
|
||||||
|
let entry = TimetableEntry.current(variant: .week)
|
||||||
|
let next = Calendar.current.date(byAdding: .minute, value: 30, to: Date()) ?? Date()
|
||||||
|
completion(Timeline(entries: [entry], policy: .after(next)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Entry
|
||||||
|
|
||||||
|
enum TimetableVariant { case day, week }
|
||||||
|
|
||||||
|
struct TimetableEntry: TimelineEntry {
|
||||||
|
let date: Date
|
||||||
|
let variant: TimetableVariant
|
||||||
|
let data: WidgetTimetableData?
|
||||||
|
let isLoggedIn: Bool
|
||||||
|
let themeMode: String
|
||||||
|
|
||||||
|
static func placeholder() -> TimetableEntry {
|
||||||
|
TimetableEntry(
|
||||||
|
date: Date(),
|
||||||
|
variant: .day,
|
||||||
|
data: nil,
|
||||||
|
isLoggedIn: true,
|
||||||
|
themeMode: "system"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func current(variant: TimetableVariant) -> TimetableEntry {
|
||||||
|
let isLoggedIn = WidgetDataLoader.isLoggedIn()
|
||||||
|
let data = isLoggedIn
|
||||||
|
? (variant == .day ? WidgetDataLoader.loadDay() : WidgetDataLoader.loadWeek())
|
||||||
|
: nil
|
||||||
|
return TimetableEntry(
|
||||||
|
date: Date(),
|
||||||
|
variant: variant,
|
||||||
|
data: data,
|
||||||
|
isLoggedIn: isLoggedIn,
|
||||||
|
themeMode: WidgetDataLoader.themeMode()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
/// Applies the user's chosen light/dark override on top of the system
|
||||||
|
/// scheme so the widget honours the in-app theme setting.
|
||||||
|
@ViewBuilder
|
||||||
|
func widgetThemeOverride(_ mode: String) -> some View {
|
||||||
|
switch mode {
|
||||||
|
case "light": self.environment(\.colorScheme, .light)
|
||||||
|
case "dark": self.environment(\.colorScheme, .dark)
|
||||||
|
default: self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps the widget view in the Marianum palette + container background
|
||||||
|
/// so all subviews can read `\.widgetPalette` and so the widget renders
|
||||||
|
/// in our warm off-white / dark-clay instead of system grey.
|
||||||
|
@ViewBuilder
|
||||||
|
func widgetSurface(entry: TimetableEntry) -> some View {
|
||||||
|
WidgetSurface(entry: entry) { self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct WidgetSurface<Content: View>: View {
|
||||||
|
let entry: TimetableEntry
|
||||||
|
@ViewBuilder let content: () -> Content
|
||||||
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let palette = WidgetPalette.resolve(
|
||||||
|
themeMode: entry.themeMode,
|
||||||
|
colorScheme: colorScheme
|
||||||
|
)
|
||||||
|
return AnyView(
|
||||||
|
background(content: content(), palette: palette)
|
||||||
|
.environment(\.widgetPalette, palette)
|
||||||
|
.widgetThemeOverride(entry.themeMode)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func background<C: View>(content: C, palette: WidgetPalette) -> some View {
|
||||||
|
if #available(iOS 17.0, *) {
|
||||||
|
content.containerBackground(palette.background, for: .widget)
|
||||||
|
} else {
|
||||||
|
content.background(palette.background)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Mirrors lib/widget_data/widget_data.dart. JSON keys must stay in sync —
|
||||||
|
/// the bridge is one-way: Dart writes, Swift reads.
|
||||||
|
enum WidgetLessonStatus: String, Codable {
|
||||||
|
case regular
|
||||||
|
case ongoing
|
||||||
|
case past
|
||||||
|
case cancelled
|
||||||
|
case irregular
|
||||||
|
case teacherChanged
|
||||||
|
case event
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WidgetLesson: Codable {
|
||||||
|
let start: Date
|
||||||
|
let end: Date
|
||||||
|
let subjectShort: String
|
||||||
|
let subjectLong: String?
|
||||||
|
let room: String?
|
||||||
|
let teacher: String?
|
||||||
|
let originalTeacher: String?
|
||||||
|
let status: WidgetLessonStatus
|
||||||
|
let customColor: String?
|
||||||
|
let siblingCount: Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WidgetPeriod: Codable {
|
||||||
|
let name: String
|
||||||
|
let startMinutes: Int
|
||||||
|
let endMinutes: Int
|
||||||
|
let virtualStartMinutes: Int
|
||||||
|
let virtualEndMinutes: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WidgetTimetableData: Codable {
|
||||||
|
let fetchedAt: Date
|
||||||
|
let anchorDate: Date
|
||||||
|
let lessons: [WidgetLesson]
|
||||||
|
let periods: [WidgetPeriod]
|
||||||
|
let isHoliday: Bool
|
||||||
|
let holidayName: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WidgetDataKey {
|
||||||
|
static let appGroupId = "group.eu.mhsl.marianum.mobile.client.widget"
|
||||||
|
static let dayData = "widget_data_day_v1"
|
||||||
|
static let weekData = "widget_data_week_v1"
|
||||||
|
static let loggedIn = "widget_data_logged_in_v1"
|
||||||
|
static let themeMode = "widget_setting_theme_mode_v1"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WidgetDataLoader {
|
||||||
|
/// Dart's `DateTime.toIso8601String()` on a non-UTC DateTime drops the
|
||||||
|
/// trailing Z and ships local wall-clock time. ISO8601DateFormatter's
|
||||||
|
/// default treats that as UTC and shifts every lesson by the local TZ
|
||||||
|
/// offset — dispatch by suffix instead, mirroring WidgetDataParser.kt.
|
||||||
|
private static func parseDartDate(_ raw: String) -> Date? {
|
||||||
|
let hasTzSuffix = raw.hasSuffix("Z")
|
||||||
|
|| raw.range(of: #"[+-]\d{2}:?\d{2}$"#, options: .regularExpression) != nil
|
||||||
|
if hasTzSuffix {
|
||||||
|
let iso = ISO8601DateFormatter()
|
||||||
|
iso.formatOptions = [.withFullDate, .withFullTime, .withFractionalSeconds]
|
||||||
|
if let d = iso.date(from: raw) { return d }
|
||||||
|
iso.formatOptions = [.withFullDate, .withFullTime]
|
||||||
|
return iso.date(from: raw)
|
||||||
|
}
|
||||||
|
for pattern in [
|
||||||
|
"yyyy-MM-dd'T'HH:mm:ss.SSSSSS",
|
||||||
|
"yyyy-MM-dd'T'HH:mm:ss.SSS",
|
||||||
|
"yyyy-MM-dd'T'HH:mm:ss",
|
||||||
|
] {
|
||||||
|
let f = DateFormatter()
|
||||||
|
f.dateFormat = pattern
|
||||||
|
f.timeZone = TimeZone.current
|
||||||
|
f.locale = Locale(identifier: "en_US_POSIX")
|
||||||
|
if let d = f.date(from: raw) { return d }
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func decoder() -> JSONDecoder {
|
||||||
|
let dec = JSONDecoder()
|
||||||
|
dec.dateDecodingStrategy = .custom { decoder in
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
let raw = try container.decode(String.self)
|
||||||
|
if let d = parseDartDate(raw) { return d }
|
||||||
|
throw DecodingError.dataCorruptedError(
|
||||||
|
in: container,
|
||||||
|
debugDescription: "Unparseable date: \(raw)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return dec
|
||||||
|
}
|
||||||
|
|
||||||
|
static func loadDay() -> WidgetTimetableData? {
|
||||||
|
load(key: WidgetDataKey.dayData)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func loadWeek() -> WidgetTimetableData? {
|
||||||
|
load(key: WidgetDataKey.weekData)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func isLoggedIn() -> Bool {
|
||||||
|
let defaults = UserDefaults(suiteName: WidgetDataKey.appGroupId)
|
||||||
|
return defaults?.bool(forKey: WidgetDataKey.loggedIn) ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "light" / "dark" / "system". The view's `.environment(\.colorScheme)`
|
||||||
|
/// reads this so the App's theme choice wins over the OS-level setting.
|
||||||
|
static func themeMode() -> String {
|
||||||
|
let defaults = UserDefaults(suiteName: WidgetDataKey.appGroupId)
|
||||||
|
return defaults?.string(forKey: WidgetDataKey.themeMode) ?? "system"
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func load(key: String) -> WidgetTimetableData? {
|
||||||
|
guard let defaults = UserDefaults(suiteName: WidgetDataKey.appGroupId),
|
||||||
|
let raw = defaults.string(forKey: key),
|
||||||
|
let data = raw.data(using: .utf8) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
return try decoder().decode(WidgetTimetableData.self, from: data)
|
||||||
|
} catch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// Mirrors the Kotlin `WidgetPalette` in WidgetRenderer.kt so day/week widgets
|
||||||
|
/// look identical across platforms. All values are hex tokens from the in-app
|
||||||
|
/// LightAppTheme / DarkAppTheme — do not swap to system colors, the whole
|
||||||
|
/// point is platform-independent branding.
|
||||||
|
struct WidgetPalette {
|
||||||
|
let background: Color
|
||||||
|
let textPrimary: Color
|
||||||
|
let textSecondary: Color
|
||||||
|
let divider: Color
|
||||||
|
let breakBlock: Color
|
||||||
|
let watermarkOpacity: Double
|
||||||
|
|
||||||
|
static let light = WidgetPalette(
|
||||||
|
background: Color(red: 0xFC / 255, green: 0xF7 / 255, blue: 0xF5 / 255),
|
||||||
|
textPrimary: Color(red: 0x11 / 255, green: 0x11 / 255, blue: 0x11 / 255),
|
||||||
|
textSecondary: Color(red: 0x55 / 255, green: 0x55 / 255, blue: 0x55 / 255),
|
||||||
|
divider: Color.black.opacity(0x22 / 255.0),
|
||||||
|
breakBlock: Color.black.opacity(0x0C / 255.0),
|
||||||
|
watermarkOpacity: 0.014
|
||||||
|
)
|
||||||
|
|
||||||
|
static let dark = WidgetPalette(
|
||||||
|
background: Color(red: 0x1F / 255, green: 0x17 / 255, blue: 0x16 / 255),
|
||||||
|
textPrimary: Color(red: 0xF1 / 255, green: 0xF1 / 255, blue: 0xF1 / 255),
|
||||||
|
textSecondary: Color(red: 0xB0 / 255, green: 0xB0 / 255, blue: 0xB0 / 255),
|
||||||
|
divider: Color.white.opacity(0x33 / 255.0),
|
||||||
|
breakBlock: Color.white.opacity(0x14 / 255.0),
|
||||||
|
watermarkOpacity: 0.025
|
||||||
|
)
|
||||||
|
|
||||||
|
static func resolve(themeMode: String, colorScheme: ColorScheme) -> WidgetPalette {
|
||||||
|
let isDark: Bool
|
||||||
|
switch themeMode {
|
||||||
|
case "light": isDark = false
|
||||||
|
case "dark": isDark = true
|
||||||
|
default: isDark = colorScheme == .dark
|
||||||
|
}
|
||||||
|
return isDark ? .dark : .light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct WidgetPaletteKey: EnvironmentKey {
|
||||||
|
static let defaultValue = WidgetPalette.light
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
var widgetPalette: WidgetPalette {
|
||||||
|
get { self[WidgetPaletteKey.self] }
|
||||||
|
set { self[WidgetPaletteKey.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
class ApiParams {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
class ApiRequest {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
class ApiError {
|
class ApiError implements Exception {
|
||||||
String message;
|
String message;
|
||||||
|
|
||||||
ApiError(this.message);
|
ApiError(this.message);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
class ApiParams {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
class ApiRequest {}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
abstract class ApiResponse {
|
abstract class ApiResponse {
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
late http.Response rawResponse;
|
late http.Response rawResponse;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
abstract class AppException implements Exception {
|
||||||
|
final String userMessage;
|
||||||
|
final String? technicalDetails;
|
||||||
|
final bool allowRetry;
|
||||||
|
|
||||||
|
const AppException({
|
||||||
|
required this.userMessage,
|
||||||
|
this.technicalDetails,
|
||||||
|
this.allowRetry = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => technicalDetails == null
|
||||||
|
? '$runtimeType: $userMessage'
|
||||||
|
: '$runtimeType: $userMessage ($technicalDetails)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import 'app_exception.dart';
|
||||||
|
|
||||||
|
class AuthException extends AppException {
|
||||||
|
final int statusCode;
|
||||||
|
|
||||||
|
const AuthException({
|
||||||
|
required this.statusCode,
|
||||||
|
required super.userMessage,
|
||||||
|
super.technicalDetails,
|
||||||
|
}) : super(allowRetry: false);
|
||||||
|
|
||||||
|
factory AuthException.unauthorized({String? technicalDetails}) =>
|
||||||
|
AuthException(
|
||||||
|
statusCode: 401,
|
||||||
|
userMessage: 'Bitte melde dich erneut an, um fortzufahren.',
|
||||||
|
technicalDetails: technicalDetails,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory AuthException.forbidden({String? technicalDetails}) => AuthException(
|
||||||
|
statusCode: 403,
|
||||||
|
userMessage: 'Du hast keine Berechtigung für diese Aktion.',
|
||||||
|
technicalDetails: technicalDetails,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import '../api_error.dart';
|
||||||
|
import '../marianumcloud/talk/talk_error.dart';
|
||||||
|
import '../webuntis/webuntis_error.dart';
|
||||||
|
import 'app_exception.dart';
|
||||||
|
import 'network_exception.dart';
|
||||||
|
import 'parse_exception.dart';
|
||||||
|
import 'server_exception.dart';
|
||||||
|
import 'talk_exception.dart';
|
||||||
|
import 'webuntis_exception.dart';
|
||||||
|
|
||||||
|
const String _defaultFallback =
|
||||||
|
'Etwas ist schiefgelaufen. Bitte versuche es erneut.';
|
||||||
|
const String _tlsErrorMessage =
|
||||||
|
'Die sichere Verbindung zum Server wurde abgelehnt (Zertifikat oder TLS-Fehler). '
|
||||||
|
'Häufige Ursachen: falsche Geräte-Uhrzeit oder ein WLAN mit Anmeldeseite (z.B. Café/Hotel).';
|
||||||
|
|
||||||
|
AppException? _dioToAppException(DioException error) {
|
||||||
|
switch (error.type) {
|
||||||
|
case DioExceptionType.connectionTimeout:
|
||||||
|
case DioExceptionType.sendTimeout:
|
||||||
|
case DioExceptionType.receiveTimeout:
|
||||||
|
return NetworkException.timeout(technicalDetails: error.message);
|
||||||
|
case DioExceptionType.connectionError:
|
||||||
|
return NetworkException(technicalDetails: error.message);
|
||||||
|
case DioExceptionType.badCertificate:
|
||||||
|
return const NetworkException(userMessage: _tlsErrorMessage);
|
||||||
|
case DioExceptionType.badResponse:
|
||||||
|
final status = error.response?.statusCode;
|
||||||
|
return ServerException(
|
||||||
|
statusCode: status ?? -1,
|
||||||
|
technicalDetails: 'HTTP $status: ${error.message}',
|
||||||
|
);
|
||||||
|
case DioExceptionType.cancel:
|
||||||
|
case DioExceptionType.unknown:
|
||||||
|
final inner = error.error;
|
||||||
|
if (inner is SocketException) {
|
||||||
|
return NetworkException(technicalDetails: inner.message);
|
||||||
|
}
|
||||||
|
if (inner is HandshakeException) {
|
||||||
|
return const NetworkException(userMessage: _tlsErrorMessage);
|
||||||
|
}
|
||||||
|
if (inner is FormatException) {
|
||||||
|
return ParseException(technicalDetails: inner.message);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String errorToUserMessage(Object? error, {String fallback = _defaultFallback}) {
|
||||||
|
if (error == null) return fallback;
|
||||||
|
if (error is AppException) return error.userMessage;
|
||||||
|
|
||||||
|
if (error is TalkError) return TalkException(error).userMessage;
|
||||||
|
if (error is WebuntisError) return WebuntisException(error).userMessage;
|
||||||
|
|
||||||
|
if (error is DioException) {
|
||||||
|
final mapped = _dioToAppException(error);
|
||||||
|
if (mapped != null) return mapped.userMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error is SocketException) {
|
||||||
|
return const NetworkException().userMessage;
|
||||||
|
}
|
||||||
|
if (error is TimeoutException) {
|
||||||
|
return NetworkException.timeout().userMessage;
|
||||||
|
}
|
||||||
|
if (error is http.ClientException) {
|
||||||
|
return const NetworkException().userMessage;
|
||||||
|
}
|
||||||
|
if (error is HandshakeException) {
|
||||||
|
return _tlsErrorMessage;
|
||||||
|
}
|
||||||
|
if (error is FormatException) {
|
||||||
|
return const ParseException().userMessage;
|
||||||
|
}
|
||||||
|
if (error is ApiError) {
|
||||||
|
return _stripDioPrefix(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? errorToTechnicalDetails(Object? error) {
|
||||||
|
if (error == null) return null;
|
||||||
|
if (error is AppException) return error.technicalDetails ?? error.toString();
|
||||||
|
if (error is TalkError) return TalkException(error).technicalDetails;
|
||||||
|
if (error is WebuntisError) return WebuntisException(error).technicalDetails;
|
||||||
|
if (error is DioException) {
|
||||||
|
final mapped = _dioToAppException(error);
|
||||||
|
if (mapped != null) return mapped.technicalDetails ?? mapped.toString();
|
||||||
|
}
|
||||||
|
return error.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool errorAllowsRetry(Object? error) {
|
||||||
|
if (error == null) return true;
|
||||||
|
if (error is AppException) return error.allowRetry;
|
||||||
|
if (error is DioException) {
|
||||||
|
final mapped = _dioToAppException(error);
|
||||||
|
if (mapped != null) return mapped.allowRetry;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _stripDioPrefix(String raw) {
|
||||||
|
// ApiError messages embed full request URIs; only surface the first line.
|
||||||
|
final firstLine = raw.split('\n').first.trim();
|
||||||
|
return firstLine.isEmpty ? _defaultFallback : firstLine;
|
||||||
|
}
|
||||||